mirror of https://github.com/docker/docs.git
commit
3f5a78d235
|
@ -37,6 +37,11 @@
|
||||||
"Comment": "v2.0.0-rc.3-2-g75919b7",
|
"Comment": "v2.0.0-rc.3-2-g75919b7",
|
||||||
"Rev": "75919b7dcc5d53894d7e8f1584e91ae148335f3a"
|
"Rev": "75919b7dcc5d53894d7e8f1584e91ae148335f3a"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/docker/docker/pkg/term",
|
||||||
|
"Comment": "v1.4.1-775-g70fbd45",
|
||||||
|
"Rev": "70fbd45a5c88f6f39a07b04f81a07721bf5f3eed"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/docker/libtrust",
|
"ImportPath": "github.com/docker/libtrust",
|
||||||
"Rev": "fa567046d9b14f6aa788882a950d69651d230b21"
|
"Rev": "fa567046d9b14f6aa788882a950d69651d230b21"
|
||||||
|
@ -47,7 +52,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/endophage/gotuf",
|
"ImportPath": "github.com/endophage/gotuf",
|
||||||
"Rev": "af7152a51a0663dc47768e591ffdfa1f60e5308a"
|
"Rev": "ab4ba80203ffa5bfd742e6891bd28bfbf43a9453"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/go-sql-driver/mysql",
|
"ImportPath": "github.com/go-sql-driver/mysql",
|
||||||
|
|
1
Godeps/_workspace/src/github.com/docker/docker/pkg/term/MAINTAINERS
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/docker/docker/pkg/term/MAINTAINERS
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Solomon Hykes <solomon@docker.com> (@shykes)
|
87
Godeps/_workspace/src/github.com/docker/docker/pkg/term/console_windows.go
generated
vendored
Normal file
87
Godeps/_workspace/src/github.com/docker/docker/pkg/term/console_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package term
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Consts for Get/SetConsoleMode function
|
||||||
|
// see http://msdn.microsoft.com/en-us/library/windows/desktop/ms683167(v=vs.85).aspx
|
||||||
|
ENABLE_ECHO_INPUT = 0x0004
|
||||||
|
ENABLE_INSERT_MODE = 0x0020
|
||||||
|
ENABLE_LINE_INPUT = 0x0002
|
||||||
|
ENABLE_MOUSE_INPUT = 0x0010
|
||||||
|
ENABLE_PROCESSED_INPUT = 0x0001
|
||||||
|
ENABLE_QUICK_EDIT_MODE = 0x0040
|
||||||
|
ENABLE_WINDOW_INPUT = 0x0008
|
||||||
|
// If parameter is a screen buffer handle, additional values
|
||||||
|
ENABLE_PROCESSED_OUTPUT = 0x0001
|
||||||
|
ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002
|
||||||
|
)
|
||||||
|
|
||||||
|
var kernel32DLL = syscall.NewLazyDLL("kernel32.dll")
|
||||||
|
|
||||||
|
var (
|
||||||
|
setConsoleModeProc = kernel32DLL.NewProc("SetConsoleMode")
|
||||||
|
getConsoleScreenBufferInfoProc = kernel32DLL.NewProc("GetConsoleScreenBufferInfo")
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetConsoleMode(fileDesc uintptr) (uint32, error) {
|
||||||
|
var mode uint32
|
||||||
|
err := syscall.GetConsoleMode(syscall.Handle(fileDesc), &mode)
|
||||||
|
return mode, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetConsoleMode(fileDesc uintptr, mode uint32) error {
|
||||||
|
r, _, err := setConsoleModeProc.Call(fileDesc, uintptr(mode), 0)
|
||||||
|
if r == 0 {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return syscall.EINVAL
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// types for calling GetConsoleScreenBufferInfo
|
||||||
|
// see http://msdn.microsoft.com/en-us/library/windows/desktop/ms682093(v=vs.85).aspx
|
||||||
|
type (
|
||||||
|
SHORT int16
|
||||||
|
|
||||||
|
SMALL_RECT struct {
|
||||||
|
Left SHORT
|
||||||
|
Top SHORT
|
||||||
|
Right SHORT
|
||||||
|
Bottom SHORT
|
||||||
|
}
|
||||||
|
|
||||||
|
COORD struct {
|
||||||
|
X SHORT
|
||||||
|
Y SHORT
|
||||||
|
}
|
||||||
|
|
||||||
|
WORD uint16
|
||||||
|
|
||||||
|
CONSOLE_SCREEN_BUFFER_INFO struct {
|
||||||
|
dwSize COORD
|
||||||
|
dwCursorPosition COORD
|
||||||
|
wAttributes WORD
|
||||||
|
srWindow SMALL_RECT
|
||||||
|
dwMaximumWindowSize COORD
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetConsoleScreenBufferInfo(fileDesc uintptr) (*CONSOLE_SCREEN_BUFFER_INFO, error) {
|
||||||
|
var info CONSOLE_SCREEN_BUFFER_INFO
|
||||||
|
r, _, err := getConsoleScreenBufferInfoProc.Call(uintptr(fileDesc), uintptr(unsafe.Pointer(&info)), 0)
|
||||||
|
if r == 0 {
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return nil, syscall.EINVAL
|
||||||
|
}
|
||||||
|
return &info, nil
|
||||||
|
}
|
47
Godeps/_workspace/src/github.com/docker/docker/pkg/term/tc_linux_cgo.go
generated
vendored
Normal file
47
Godeps/_workspace/src/github.com/docker/docker/pkg/term/tc_linux_cgo.go
generated
vendored
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
// +build linux,cgo
|
||||||
|
|
||||||
|
package term
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// #include <termios.h>
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
type Termios syscall.Termios
|
||||||
|
|
||||||
|
// MakeRaw put the terminal connected to the given file descriptor into raw
|
||||||
|
// mode and returns the previous state of the terminal so that it can be
|
||||||
|
// restored.
|
||||||
|
func MakeRaw(fd uintptr) (*State, error) {
|
||||||
|
var oldState State
|
||||||
|
if err := tcget(fd, &oldState.termios); err != 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newState := oldState.termios
|
||||||
|
|
||||||
|
C.cfmakeraw((*C.struct_termios)(unsafe.Pointer(&newState)))
|
||||||
|
if err := tcset(fd, &newState); err != 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &oldState, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func tcget(fd uintptr, p *Termios) syscall.Errno {
|
||||||
|
ret, err := C.tcgetattr(C.int(fd), (*C.struct_termios)(unsafe.Pointer(p)))
|
||||||
|
if ret != 0 {
|
||||||
|
return err.(syscall.Errno)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func tcset(fd uintptr, p *Termios) syscall.Errno {
|
||||||
|
ret, err := C.tcsetattr(C.int(fd), C.TCSANOW, (*C.struct_termios)(unsafe.Pointer(p)))
|
||||||
|
if ret != 0 {
|
||||||
|
return err.(syscall.Errno)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
19
Godeps/_workspace/src/github.com/docker/docker/pkg/term/tc_other.go
generated
vendored
Normal file
19
Godeps/_workspace/src/github.com/docker/docker/pkg/term/tc_other.go
generated
vendored
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
// +build !windows
|
||||||
|
// +build !linux !cgo
|
||||||
|
|
||||||
|
package term
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func tcget(fd uintptr, p *Termios) syscall.Errno {
|
||||||
|
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(getTermios), uintptr(unsafe.Pointer(p)))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func tcset(fd uintptr, p *Termios) syscall.Errno {
|
||||||
|
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, setTermios, uintptr(unsafe.Pointer(p)))
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,103 @@
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package term
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrInvalidState = errors.New("Invalid terminal state")
|
||||||
|
)
|
||||||
|
|
||||||
|
type State struct {
|
||||||
|
termios Termios
|
||||||
|
}
|
||||||
|
|
||||||
|
type Winsize struct {
|
||||||
|
Height uint16
|
||||||
|
Width uint16
|
||||||
|
x uint16
|
||||||
|
y uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetWinsize(fd uintptr) (*Winsize, error) {
|
||||||
|
ws := &Winsize{}
|
||||||
|
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(ws)))
|
||||||
|
// Skipp errno = 0
|
||||||
|
if err == 0 {
|
||||||
|
return ws, nil
|
||||||
|
}
|
||||||
|
return ws, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetWinsize(fd uintptr, ws *Winsize) error {
|
||||||
|
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(syscall.TIOCSWINSZ), uintptr(unsafe.Pointer(ws)))
|
||||||
|
// Skipp errno = 0
|
||||||
|
if err == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||||
|
func IsTerminal(fd uintptr) bool {
|
||||||
|
var termios Termios
|
||||||
|
return tcget(fd, &termios) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore restores the terminal connected to the given file descriptor to a
|
||||||
|
// previous state.
|
||||||
|
func RestoreTerminal(fd uintptr, state *State) error {
|
||||||
|
if state == nil {
|
||||||
|
return ErrInvalidState
|
||||||
|
}
|
||||||
|
if err := tcset(fd, &state.termios); err != 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SaveState(fd uintptr) (*State, error) {
|
||||||
|
var oldState State
|
||||||
|
if err := tcget(fd, &oldState.termios); err != 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &oldState, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func DisableEcho(fd uintptr, state *State) error {
|
||||||
|
newState := state.termios
|
||||||
|
newState.Lflag &^= syscall.ECHO
|
||||||
|
|
||||||
|
if err := tcset(fd, &newState); err != 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
handleInterrupt(fd, state)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetRawTerminal(fd uintptr) (*State, error) {
|
||||||
|
oldState, err := MakeRaw(fd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
handleInterrupt(fd, oldState)
|
||||||
|
return oldState, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleInterrupt(fd uintptr, state *State) {
|
||||||
|
sigchan := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sigchan, os.Interrupt)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
_ = <-sigchan
|
||||||
|
RestoreTerminal(fd, state)
|
||||||
|
os.Exit(0)
|
||||||
|
}()
|
||||||
|
}
|
89
Godeps/_workspace/src/github.com/docker/docker/pkg/term/term_windows.go
generated
vendored
Normal file
89
Godeps/_workspace/src/github.com/docker/docker/pkg/term/term_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package term
|
||||||
|
|
||||||
|
type State struct {
|
||||||
|
mode uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
type Winsize struct {
|
||||||
|
Height uint16
|
||||||
|
Width uint16
|
||||||
|
x uint16
|
||||||
|
y uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetWinsize(fd uintptr) (*Winsize, error) {
|
||||||
|
ws := &Winsize{}
|
||||||
|
var info *CONSOLE_SCREEN_BUFFER_INFO
|
||||||
|
info, err := GetConsoleScreenBufferInfo(fd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ws.Height = uint16(info.srWindow.Right - info.srWindow.Left + 1)
|
||||||
|
ws.Width = uint16(info.srWindow.Bottom - info.srWindow.Top + 1)
|
||||||
|
|
||||||
|
ws.x = 0 // todo azlinux -- this is the pixel size of the Window, and not currently used by any caller
|
||||||
|
ws.y = 0
|
||||||
|
|
||||||
|
return ws, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetWinsize(fd uintptr, ws *Winsize) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||||
|
func IsTerminal(fd uintptr) bool {
|
||||||
|
_, e := GetConsoleMode(fd)
|
||||||
|
return e == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore restores the terminal connected to the given file descriptor to a
|
||||||
|
// previous state.
|
||||||
|
func RestoreTerminal(fd uintptr, state *State) error {
|
||||||
|
return SetConsoleMode(fd, state.mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SaveState(fd uintptr) (*State, error) {
|
||||||
|
mode, e := GetConsoleMode(fd)
|
||||||
|
if e != nil {
|
||||||
|
return nil, e
|
||||||
|
}
|
||||||
|
return &State{mode}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// see http://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx for these flag settings
|
||||||
|
func DisableEcho(fd uintptr, state *State) error {
|
||||||
|
state.mode &^= (ENABLE_ECHO_INPUT)
|
||||||
|
state.mode |= (ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT)
|
||||||
|
return SetConsoleMode(fd, state.mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetRawTerminal(fd uintptr) (*State, error) {
|
||||||
|
oldState, err := MakeRaw(fd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// TODO (azlinux): implement handling interrupt and restore state of terminal
|
||||||
|
return oldState, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeRaw puts the terminal connected to the given file descriptor into raw
|
||||||
|
// mode and returns the previous state of the terminal so that it can be
|
||||||
|
// restored.
|
||||||
|
func MakeRaw(fd uintptr) (*State, error) {
|
||||||
|
var state *State
|
||||||
|
state, err := SaveState(fd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// see http://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx for these flag settings
|
||||||
|
state.mode &^= (ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT)
|
||||||
|
err = SetConsoleMode(fd, state.mode)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return state, nil
|
||||||
|
}
|
65
Godeps/_workspace/src/github.com/docker/docker/pkg/term/termios_darwin.go
generated
vendored
Normal file
65
Godeps/_workspace/src/github.com/docker/docker/pkg/term/termios_darwin.go
generated
vendored
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
package term
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
getTermios = syscall.TIOCGETA
|
||||||
|
setTermios = syscall.TIOCSETA
|
||||||
|
|
||||||
|
IGNBRK = syscall.IGNBRK
|
||||||
|
PARMRK = syscall.PARMRK
|
||||||
|
INLCR = syscall.INLCR
|
||||||
|
IGNCR = syscall.IGNCR
|
||||||
|
ECHONL = syscall.ECHONL
|
||||||
|
CSIZE = syscall.CSIZE
|
||||||
|
ICRNL = syscall.ICRNL
|
||||||
|
ISTRIP = syscall.ISTRIP
|
||||||
|
PARENB = syscall.PARENB
|
||||||
|
ECHO = syscall.ECHO
|
||||||
|
ICANON = syscall.ICANON
|
||||||
|
ISIG = syscall.ISIG
|
||||||
|
IXON = syscall.IXON
|
||||||
|
BRKINT = syscall.BRKINT
|
||||||
|
INPCK = syscall.INPCK
|
||||||
|
OPOST = syscall.OPOST
|
||||||
|
CS8 = syscall.CS8
|
||||||
|
IEXTEN = syscall.IEXTEN
|
||||||
|
)
|
||||||
|
|
||||||
|
type Termios struct {
|
||||||
|
Iflag uint64
|
||||||
|
Oflag uint64
|
||||||
|
Cflag uint64
|
||||||
|
Lflag uint64
|
||||||
|
Cc [20]byte
|
||||||
|
Ispeed uint64
|
||||||
|
Ospeed uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeRaw put the terminal connected to the given file descriptor into raw
|
||||||
|
// mode and returns the previous state of the terminal so that it can be
|
||||||
|
// restored.
|
||||||
|
func MakeRaw(fd uintptr) (*State, error) {
|
||||||
|
var oldState State
|
||||||
|
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(getTermios), uintptr(unsafe.Pointer(&oldState.termios))); err != 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newState := oldState.termios
|
||||||
|
newState.Iflag &^= (IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON)
|
||||||
|
newState.Oflag &^= OPOST
|
||||||
|
newState.Lflag &^= (ECHO | ECHONL | ICANON | ISIG | IEXTEN)
|
||||||
|
newState.Cflag &^= (CSIZE | PARENB)
|
||||||
|
newState.Cflag |= CS8
|
||||||
|
newState.Cc[syscall.VMIN] = 1
|
||||||
|
newState.Cc[syscall.VTIME] = 0
|
||||||
|
|
||||||
|
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(setTermios), uintptr(unsafe.Pointer(&newState))); err != 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &oldState, nil
|
||||||
|
}
|
65
Godeps/_workspace/src/github.com/docker/docker/pkg/term/termios_freebsd.go
generated
vendored
Normal file
65
Godeps/_workspace/src/github.com/docker/docker/pkg/term/termios_freebsd.go
generated
vendored
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
package term
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
getTermios = syscall.TIOCGETA
|
||||||
|
setTermios = syscall.TIOCSETA
|
||||||
|
|
||||||
|
IGNBRK = syscall.IGNBRK
|
||||||
|
PARMRK = syscall.PARMRK
|
||||||
|
INLCR = syscall.INLCR
|
||||||
|
IGNCR = syscall.IGNCR
|
||||||
|
ECHONL = syscall.ECHONL
|
||||||
|
CSIZE = syscall.CSIZE
|
||||||
|
ICRNL = syscall.ICRNL
|
||||||
|
ISTRIP = syscall.ISTRIP
|
||||||
|
PARENB = syscall.PARENB
|
||||||
|
ECHO = syscall.ECHO
|
||||||
|
ICANON = syscall.ICANON
|
||||||
|
ISIG = syscall.ISIG
|
||||||
|
IXON = syscall.IXON
|
||||||
|
BRKINT = syscall.BRKINT
|
||||||
|
INPCK = syscall.INPCK
|
||||||
|
OPOST = syscall.OPOST
|
||||||
|
CS8 = syscall.CS8
|
||||||
|
IEXTEN = syscall.IEXTEN
|
||||||
|
)
|
||||||
|
|
||||||
|
type Termios struct {
|
||||||
|
Iflag uint32
|
||||||
|
Oflag uint32
|
||||||
|
Cflag uint32
|
||||||
|
Lflag uint32
|
||||||
|
Cc [20]byte
|
||||||
|
Ispeed uint32
|
||||||
|
Ospeed uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeRaw put the terminal connected to the given file descriptor into raw
|
||||||
|
// mode and returns the previous state of the terminal so that it can be
|
||||||
|
// restored.
|
||||||
|
func MakeRaw(fd uintptr) (*State, error) {
|
||||||
|
var oldState State
|
||||||
|
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(getTermios), uintptr(unsafe.Pointer(&oldState.termios))); err != 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newState := oldState.termios
|
||||||
|
newState.Iflag &^= (IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON)
|
||||||
|
newState.Oflag &^= OPOST
|
||||||
|
newState.Lflag &^= (ECHO | ECHONL | ICANON | ISIG | IEXTEN)
|
||||||
|
newState.Cflag &^= (CSIZE | PARENB)
|
||||||
|
newState.Cflag |= CS8
|
||||||
|
newState.Cc[syscall.VMIN] = 1
|
||||||
|
newState.Cc[syscall.VTIME] = 0
|
||||||
|
|
||||||
|
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(setTermios), uintptr(unsafe.Pointer(&newState))); err != 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &oldState, nil
|
||||||
|
}
|
46
Godeps/_workspace/src/github.com/docker/docker/pkg/term/termios_linux.go
generated
vendored
Normal file
46
Godeps/_workspace/src/github.com/docker/docker/pkg/term/termios_linux.go
generated
vendored
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
// +build !cgo
|
||||||
|
|
||||||
|
package term
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
getTermios = syscall.TCGETS
|
||||||
|
setTermios = syscall.TCSETS
|
||||||
|
)
|
||||||
|
|
||||||
|
type Termios struct {
|
||||||
|
Iflag uint32
|
||||||
|
Oflag uint32
|
||||||
|
Cflag uint32
|
||||||
|
Lflag uint32
|
||||||
|
Cc [20]byte
|
||||||
|
Ispeed uint32
|
||||||
|
Ospeed uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeRaw put the terminal connected to the given file descriptor into raw
|
||||||
|
// mode and returns the previous state of the terminal so that it can be
|
||||||
|
// restored.
|
||||||
|
func MakeRaw(fd uintptr) (*State, error) {
|
||||||
|
var oldState State
|
||||||
|
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, getTermios, uintptr(unsafe.Pointer(&oldState.termios))); err != 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newState := oldState.termios
|
||||||
|
|
||||||
|
newState.Iflag &^= (syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK | syscall.ISTRIP | syscall.INLCR | syscall.IGNCR | syscall.ICRNL | syscall.IXON)
|
||||||
|
newState.Oflag &^= syscall.OPOST
|
||||||
|
newState.Lflag &^= (syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.IEXTEN)
|
||||||
|
newState.Cflag &^= (syscall.CSIZE | syscall.PARENB)
|
||||||
|
newState.Cflag |= syscall.CS8
|
||||||
|
|
||||||
|
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, setTermios, uintptr(unsafe.Pointer(&newState))); err != 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &oldState, nil
|
||||||
|
}
|
|
@ -41,9 +41,7 @@ func (k TUFKey) Cipher() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *TUFKey) ID() string {
|
func (k *TUFKey) ID() string {
|
||||||
logrus.Debug("Generating Key ID")
|
|
||||||
if k.id == "" {
|
if k.id == "" {
|
||||||
logrus.Debug("Generating Key ID")
|
|
||||||
pubK := NewTUFKey(k.Cipher(), k.Public(), nil)
|
pubK := NewTUFKey(k.Cipher(), k.Public(), nil)
|
||||||
data, err := cjson.Marshal(&pubK)
|
data, err := cjson.Marshal(&pubK)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -23,7 +23,7 @@ type Snapshot struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSnapshot(root *Signed, targets *Signed) (*SignedSnapshot, error) {
|
func NewSnapshot(root *Signed, targets *Signed) (*SignedSnapshot, error) {
|
||||||
logrus.Debug("NewSnapshot")
|
logrus.Debug("generating new snapshot...")
|
||||||
targetsJSON, err := json.Marshal(targets)
|
targetsJSON, err := json.Marshal(targets)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Debug("Error Marshalling Targets")
|
logrus.Debug("Error Marshalling Targets")
|
||||||
|
|
|
@ -13,7 +13,17 @@ import (
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultHashAlgorithm = "sha256"
|
const (
|
||||||
|
defaultHashAlgorithm = "sha256"
|
||||||
|
EDDSASignature = "eddsa"
|
||||||
|
RSAPSSSignature = "rsapss"
|
||||||
|
ECDSASignature = "ecdsa"
|
||||||
|
RSAKey = "rsa"
|
||||||
|
RSAx509Key = "rsa-x509"
|
||||||
|
ECDSAKey = "ecdsa"
|
||||||
|
ECDSAx509Key = "ecdsa-x509"
|
||||||
|
PyCryptoSignature = "pycrypto-pkcs#1 pss"
|
||||||
|
)
|
||||||
|
|
||||||
var TUFTypes = map[string]string{
|
var TUFTypes = map[string]string{
|
||||||
"targets": "Targets",
|
"targets": "Targets",
|
||||||
|
|
|
@ -18,29 +18,27 @@ func NewSigner(service CryptoService) *Signer {
|
||||||
// Sign takes a data.Signed and a key, calculated and adds the signature
|
// Sign takes a data.Signed and a key, calculated and adds the signature
|
||||||
// to the data.Signed
|
// to the data.Signed
|
||||||
func (signer *Signer) Sign(s *data.Signed, keys ...*data.PublicKey) error {
|
func (signer *Signer) Sign(s *data.Signed, keys ...*data.PublicKey) error {
|
||||||
logrus.Debug("signed/sign.go:Sign")
|
logrus.Debugf("sign called with %d keys", len(keys))
|
||||||
signatures := make([]data.Signature, 0, len(s.Signatures)+1)
|
signatures := make([]data.Signature, 0, len(s.Signatures)+1)
|
||||||
keyIDMemb := make(map[string]struct{})
|
keyIDMemb := make(map[string]struct{})
|
||||||
keyIDs := make([]string, 0, len(keys))
|
keyIDs := make([]string, 0, len(keys))
|
||||||
logrus.Debug("Generate list of signing IDs")
|
|
||||||
for _, key := range keys {
|
for _, key := range keys {
|
||||||
keyIDMemb[key.ID()] = struct{}{}
|
keyIDMemb[key.ID()] = struct{}{}
|
||||||
keyIDs = append(keyIDs, key.ID())
|
keyIDs = append(keyIDs, key.ID())
|
||||||
}
|
}
|
||||||
logrus.Debug("Filter out sigs we will be resigning")
|
logrus.Debugf("Generated list of signing IDs: %v", keyIDs)
|
||||||
for _, sig := range s.Signatures {
|
for _, sig := range s.Signatures {
|
||||||
if _, ok := keyIDMemb[sig.KeyID]; ok {
|
if _, ok := keyIDMemb[sig.KeyID]; ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
signatures = append(signatures, sig)
|
signatures = append(signatures, sig)
|
||||||
}
|
}
|
||||||
logrus.Debug("Performing Signing")
|
|
||||||
newSigs, err := signer.service.Sign(keyIDs, s.Signed)
|
newSigs, err := signer.service.Sign(keyIDs, s.Signed)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
logrus.Debugf("appending %d new signatures", len(newSigs))
|
||||||
logrus.Debug("Updating signatures slice")
|
|
||||||
s.Signatures = append(signatures, newSigs...)
|
s.Signatures = append(signatures, newSigs...)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,7 @@ var _ CryptoService = &MockCryptoService{}
|
||||||
// Test signing and ensure the expected signature is added
|
// Test signing and ensure the expected signature is added
|
||||||
func TestBasicSign(t *testing.T) {
|
func TestBasicSign(t *testing.T) {
|
||||||
testKey, _ := pem.Decode([]byte(testKeyPEM1))
|
testKey, _ := pem.Decode([]byte(testKeyPEM1))
|
||||||
k := data.NewPublicKey("RSA", testKey.Bytes)
|
k := data.NewPublicKey(data.RSAKey, testKey.Bytes)
|
||||||
signer := Signer{&MockCryptoService{
|
signer := Signer{&MockCryptoService{
|
||||||
testKey: *k,
|
testKey: *k,
|
||||||
}}
|
}}
|
||||||
|
@ -68,7 +68,7 @@ func TestBasicSign(t *testing.T) {
|
||||||
// should be cleaning previous signatures by the KeyID when asked to sign again)
|
// should be cleaning previous signatures by the KeyID when asked to sign again)
|
||||||
func TestReSign(t *testing.T) {
|
func TestReSign(t *testing.T) {
|
||||||
testKey, _ := pem.Decode([]byte(testKeyPEM1))
|
testKey, _ := pem.Decode([]byte(testKeyPEM1))
|
||||||
k := data.NewPublicKey("RSA", testKey.Bytes)
|
k := data.NewPublicKey(data.RSAKey, testKey.Bytes)
|
||||||
signer := Signer{&MockCryptoService{
|
signer := Signer{&MockCryptoService{
|
||||||
testKey: *k,
|
testKey: *k,
|
||||||
}}
|
}}
|
||||||
|
@ -92,11 +92,11 @@ func TestMultiSign(t *testing.T) {
|
||||||
testData := data.Signed{}
|
testData := data.Signed{}
|
||||||
|
|
||||||
testKey, _ := pem.Decode([]byte(testKeyPEM1))
|
testKey, _ := pem.Decode([]byte(testKeyPEM1))
|
||||||
key := data.NewPublicKey("RSA", testKey.Bytes)
|
key := data.NewPublicKey(data.RSAKey, testKey.Bytes)
|
||||||
signer.Sign(&testData, key)
|
signer.Sign(&testData, key)
|
||||||
|
|
||||||
testKey, _ = pem.Decode([]byte(testKeyPEM2))
|
testKey, _ = pem.Decode([]byte(testKeyPEM2))
|
||||||
key = data.NewPublicKey("RSA", testKey.Bytes)
|
key = data.NewPublicKey(data.RSAKey, testKey.Bytes)
|
||||||
signer.Sign(&testData, key)
|
signer.Sign(&testData, key)
|
||||||
|
|
||||||
if len(testData.Signatures) != 2 {
|
if len(testData.Signatures) != 2 {
|
||||||
|
@ -114,7 +114,7 @@ func TestMultiSign(t *testing.T) {
|
||||||
|
|
||||||
func TestCreate(t *testing.T) {
|
func TestCreate(t *testing.T) {
|
||||||
testKey, _ := pem.Decode([]byte(testKeyPEM1))
|
testKey, _ := pem.Decode([]byte(testKeyPEM1))
|
||||||
k := data.NewPublicKey("RSA", testKey.Bytes)
|
k := data.NewPublicKey(data.RSAKey, testKey.Bytes)
|
||||||
signer := Signer{&MockCryptoService{
|
signer := Signer{&MockCryptoService{
|
||||||
testKey: *k,
|
testKey: *k,
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -2,10 +2,12 @@ package signed
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto"
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
|
"math/big"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
|
@ -17,10 +19,10 @@ import (
|
||||||
// can be injected into a verificationService. For testing and configuration
|
// can be injected into a verificationService. For testing and configuration
|
||||||
// purposes, it will not be used by default.
|
// purposes, it will not be used by default.
|
||||||
var Verifiers = map[string]Verifier{
|
var Verifiers = map[string]Verifier{
|
||||||
"ed25519": Ed25519Verifier{},
|
data.RSAPSSSignature: RSAPSSVerifier{},
|
||||||
"rsassa-pss": RSAPSSVerifier{},
|
data.PyCryptoSignature: RSAPyCryptoVerifier{},
|
||||||
"rsassa-pss-x509": RSAPSSX509Verifier{},
|
data.ECDSASignature: ECDSAVerifier{},
|
||||||
"pycrypto-pkcs#1 pss": RSAPyCryptoVerifier{},
|
data.EDDSASignature: Ed25519Verifier{},
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterVerifier provides a convenience function for init() functions
|
// RegisterVerifier provides a convenience function for init() functions
|
||||||
|
@ -31,12 +33,12 @@ func RegisterVerifier(name string, v Verifier) {
|
||||||
typOld := reflect.TypeOf(curr)
|
typOld := reflect.TypeOf(curr)
|
||||||
typNew := reflect.TypeOf(v)
|
typNew := reflect.TypeOf(v)
|
||||||
logrus.Debugf(
|
logrus.Debugf(
|
||||||
"Replacing already loaded verifier %s:%s with %s:%s",
|
"replacing already loaded verifier %s:%s with %s:%s",
|
||||||
typOld.PkgPath(), typOld.Name(),
|
typOld.PkgPath(), typOld.Name(),
|
||||||
typNew.PkgPath(), typNew.Name(),
|
typNew.PkgPath(), typNew.Name(),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
logrus.Debug("Adding verifier for: ", name)
|
logrus.Debug("adding verifier for: ", name)
|
||||||
}
|
}
|
||||||
Verifiers[name] = v
|
Verifiers[name] = v
|
||||||
}
|
}
|
||||||
|
@ -46,7 +48,7 @@ type Ed25519Verifier struct{}
|
||||||
func (v Ed25519Verifier) Verify(key data.Key, sig []byte, msg []byte) error {
|
func (v Ed25519Verifier) Verify(key data.Key, sig []byte, msg []byte) error {
|
||||||
var sigBytes [ed25519.SignatureSize]byte
|
var sigBytes [ed25519.SignatureSize]byte
|
||||||
if len(sig) != len(sigBytes) {
|
if len(sig) != len(sigBytes) {
|
||||||
logrus.Infof("Signature length is incorrect, must be %d, was %d.", ed25519.SignatureSize, len(sig))
|
logrus.Infof("signature length is incorrect, must be %d, was %d.", ed25519.SignatureSize, len(sig))
|
||||||
return ErrInvalid
|
return ErrInvalid
|
||||||
}
|
}
|
||||||
copy(sigBytes[:], sig)
|
copy(sigBytes[:], sig)
|
||||||
|
@ -55,7 +57,7 @@ func (v Ed25519Verifier) Verify(key data.Key, sig []byte, msg []byte) error {
|
||||||
copy(keyBytes[:], key.Public())
|
copy(keyBytes[:], key.Public())
|
||||||
|
|
||||||
if !ed25519.Verify(&keyBytes, msg, &sigBytes) {
|
if !ed25519.Verify(&keyBytes, msg, &sigBytes) {
|
||||||
logrus.Infof("Failed ed25519 verification")
|
logrus.Infof("failed ed25519 verification")
|
||||||
return ErrInvalid
|
return ErrInvalid
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -64,13 +66,13 @@ func (v Ed25519Verifier) Verify(key data.Key, sig []byte, msg []byte) error {
|
||||||
func verifyPSS(key interface{}, digest, sig []byte) error {
|
func verifyPSS(key interface{}, digest, sig []byte) error {
|
||||||
rsaPub, ok := key.(*rsa.PublicKey)
|
rsaPub, ok := key.(*rsa.PublicKey)
|
||||||
if !ok {
|
if !ok {
|
||||||
logrus.Infof("Value was not an RSA public key")
|
logrus.Infof("value was not an RSA public key")
|
||||||
return ErrInvalid
|
return ErrInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := rsa.PSSOptions{SaltLength: sha256.Size, Hash: crypto.SHA256}
|
opts := rsa.PSSOptions{SaltLength: sha256.Size, Hash: crypto.SHA256}
|
||||||
if err := rsa.VerifyPSS(rsaPub, crypto.SHA256, digest[:], sig, &opts); err != nil {
|
if err := rsa.VerifyPSS(rsaPub, crypto.SHA256, digest[:], sig, &opts); err != nil {
|
||||||
logrus.Infof("Failed verification: %s", err)
|
logrus.Infof("failed RSAPSS verification: %s", err)
|
||||||
return ErrInvalid
|
return ErrInvalid
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -81,15 +83,37 @@ type RSAPSSVerifier struct{}
|
||||||
|
|
||||||
// Verify does the actual check.
|
// Verify does the actual check.
|
||||||
func (v RSAPSSVerifier) Verify(key data.Key, sig []byte, msg []byte) error {
|
func (v RSAPSSVerifier) Verify(key data.Key, sig []byte, msg []byte) error {
|
||||||
digest := sha256.Sum256(msg)
|
cipher := key.Cipher()
|
||||||
|
var pubKey crypto.PublicKey
|
||||||
|
|
||||||
pub, err := x509.ParsePKIXPublicKey(key.Public())
|
switch cipher {
|
||||||
if err != nil {
|
case data.RSAx509Key:
|
||||||
logrus.Infof("Failed to parse public key: %s\n", err)
|
pemCert, _ := pem.Decode([]byte(key.Public()))
|
||||||
|
if pemCert == nil {
|
||||||
|
logrus.Infof("failed to decode PEM-encoded x509 certificate")
|
||||||
|
return ErrInvalid
|
||||||
|
}
|
||||||
|
cert, err := x509.ParseCertificate(pemCert.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Infof("failed to parse x509 certificate: %s\n", err)
|
||||||
|
return ErrInvalid
|
||||||
|
}
|
||||||
|
pubKey = cert.PublicKey
|
||||||
|
case data.RSAKey:
|
||||||
|
var err error
|
||||||
|
pubKey, err = x509.ParsePKIXPublicKey(key.Public())
|
||||||
|
if err != nil {
|
||||||
|
logrus.Infof("failed to parse public key: %s\n", err)
|
||||||
|
return ErrInvalid
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
logrus.Infof("invalid key type for RSAPSS verifier: %s", cipher)
|
||||||
return ErrInvalid
|
return ErrInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
return verifyPSS(pub, digest[:], sig)
|
digest := sha256.Sum256(msg)
|
||||||
|
|
||||||
|
return verifyPSS(pubKey, digest[:], sig)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RSAPSSVerifier checks RSASSA-PSS signatures
|
// RSAPSSVerifier checks RSASSA-PSS signatures
|
||||||
|
@ -103,37 +127,76 @@ func (v RSAPyCryptoVerifier) Verify(key data.Key, sig []byte, msg []byte) error
|
||||||
|
|
||||||
k, _ := pem.Decode([]byte(key.Public()))
|
k, _ := pem.Decode([]byte(key.Public()))
|
||||||
if k == nil {
|
if k == nil {
|
||||||
logrus.Infof("Failed to decode PEM-encoded x509 certificate")
|
logrus.Infof("failed to decode PEM-encoded x509 certificate")
|
||||||
return ErrInvalid
|
return ErrInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
pub, err := x509.ParsePKIXPublicKey(k.Bytes)
|
pub, err := x509.ParsePKIXPublicKey(k.Bytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Infof("Failed to parse public key: %s\n", err)
|
logrus.Infof("failed to parse public key: %s\n", err)
|
||||||
return ErrInvalid
|
return ErrInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
return verifyPSS(pub, digest[:], sig)
|
return verifyPSS(pub, digest[:], sig)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RSAPSSPEMVerifier checks RSASSA-PSS signatures, extracting the public key
|
// ECDSAVerifier checks ECDSA signatures, decoding the keyType appropriately
|
||||||
// from an X509 certificate.
|
type ECDSAVerifier struct{}
|
||||||
type RSAPSSX509Verifier struct{}
|
|
||||||
|
|
||||||
// Verify does the actual check.
|
// Verify does the actual check.
|
||||||
func (v RSAPSSX509Verifier) Verify(key data.Key, sig []byte, msg []byte) error {
|
func (v ECDSAVerifier) Verify(key data.Key, sig []byte, msg []byte) error {
|
||||||
|
cipher := key.Cipher()
|
||||||
|
var pubKey crypto.PublicKey
|
||||||
|
|
||||||
|
switch cipher {
|
||||||
|
case data.ECDSAx509Key:
|
||||||
|
pemCert, _ := pem.Decode([]byte(key.Public()))
|
||||||
|
if pemCert == nil {
|
||||||
|
logrus.Infof("failed to decode PEM-encoded x509 certificate for keyID: %s", key.ID())
|
||||||
|
logrus.Debugf("certificate bytes: %s", string(key.Public()))
|
||||||
|
return ErrInvalid
|
||||||
|
}
|
||||||
|
cert, err := x509.ParseCertificate(pemCert.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Infof("failed to parse x509 certificate: %s\n", err)
|
||||||
|
return ErrInvalid
|
||||||
|
}
|
||||||
|
pubKey = cert.PublicKey
|
||||||
|
case data.ECDSAKey:
|
||||||
|
var err error
|
||||||
|
pubKey, err = x509.ParsePKIXPublicKey(key.Public())
|
||||||
|
if err != nil {
|
||||||
|
logrus.Infof("Failed to parse private key for keyID: %s, %s\n", key.ID(), err)
|
||||||
|
return ErrInvalid
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
logrus.Infof("invalid key type for ECDSA verifier: %s", cipher)
|
||||||
|
return ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
ecdsaPubKey, ok := pubKey.(*ecdsa.PublicKey)
|
||||||
|
if !ok {
|
||||||
|
logrus.Infof("value isn't an ECDSA public key")
|
||||||
|
return ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
sigLength := len(sig)
|
||||||
|
expectedOctetLength := 2 * ((ecdsaPubKey.Params().BitSize + 7) >> 3)
|
||||||
|
if sigLength != expectedOctetLength {
|
||||||
|
logrus.Infof("signature had an unexpected length")
|
||||||
|
return ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
rBytes, sBytes := sig[:sigLength/2], sig[sigLength/2:]
|
||||||
|
r := new(big.Int).SetBytes(rBytes)
|
||||||
|
s := new(big.Int).SetBytes(sBytes)
|
||||||
|
|
||||||
digest := sha256.Sum256(msg)
|
digest := sha256.Sum256(msg)
|
||||||
|
|
||||||
k, _ := pem.Decode([]byte(key.Public()))
|
if !ecdsa.Verify(ecdsaPubKey, digest[:], r, s) {
|
||||||
if k == nil {
|
logrus.Infof("failed ECDSA signature validation")
|
||||||
logrus.Infof("Failed to decode PEM-encoded x509 certificate")
|
|
||||||
return ErrInvalid
|
|
||||||
}
|
|
||||||
cert, err := x509.ParseCertificate(k.Bytes)
|
|
||||||
if err != nil {
|
|
||||||
logrus.Infof("Failed to parse x509 certificate: %s\n", err)
|
|
||||||
return ErrInvalid
|
return ErrInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
return verifyPSS(cert.PublicKey, digest[:], sig)
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,298 @@
|
||||||
package signed
|
package signed
|
||||||
|
|
||||||
import (
|
import (
|
||||||
_ "crypto"
|
"bytes"
|
||||||
_ "crypto/rsa"
|
"crypto"
|
||||||
_ "crypto/sha256"
|
"crypto/ecdsa"
|
||||||
_ "crypto/x509"
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/endophage/gotuf/data"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRSAVerify(t *testing.T) {
|
type KeyTemplate struct {
|
||||||
|
KeyType string
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseRSAKey = `{"keytype":"{{.KeyType}}","keyval":{"public":"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyyvBtTg2xzYS+MTTIBqSpI4V78tt8Yzqi7Jki/Z6NqjiDvcnbgcTqNR2t6B2W5NjGdp/hSaT2jyHM+kdmEGaPxg/zIuHbL3NIp4e0qwovWiEgACPIaELdn8O/kt5swsSKl1KMvLCH1sM86qMibNMAZ/hXOwd90TcHXCgZ91wHEAmsdjDC3dB0TT+FBgOac8RM01Y196QrZoOaDMTWh0EQfw7YbXAElhFVDFxBzDdYWbcIHSIogXQmq0CP+zaL/1WgcZZIClt2M6WCaxxF1S34wNn45gCvVZiZQ/iKWHerSr/2dGQeGo+7ezMSutRzvJ+01fInD86RS/CEtBCFZ1VyQIDAQAB","private":"MIIEpAIBAAKCAQEAyyvBtTg2xzYS+MTTIBqSpI4V78tt8Yzqi7Jki/Z6NqjiDvcnbgcTqNR2t6B2W5NjGdp/hSaT2jyHM+kdmEGaPxg/zIuHbL3NIp4e0qwovWiEgACPIaELdn8O/kt5swsSKl1KMvLCH1sM86qMibNMAZ/hXOwd90TcHXCgZ91wHEAmsdjDC3dB0TT+FBgOac8RM01Y196QrZoOaDMTWh0EQfw7YbXAElhFVDFxBzDdYWbcIHSIogXQmq0CP+zaL/1WgcZZIClt2M6WCaxxF1S34wNn45gCvVZiZQ/iKWHerSr/2dGQeGo+7ezMSutRzvJ+01fInD86RS/CEtBCFZ1VyQIDAQABAoIBAHar8FFxrE1gAGTeUpOF8fG8LIQMRwO4U6eVY7V9GpWiv6gOJTHXYFxU/aL0Ty3eQRxwy9tyVRo8EJz5pRex+e6ws1M+jLOviYqW4VocxQ8dZYd+zBvQfWmRfah7XXJ/HPUx2I05zrmR7VbGX6Bu4g5w3KnyIO61gfyQNKF2bm2Q3yblfupx3URvX0bl180R/+QN2Aslr4zxULFE6b+qJqBydrztq+AAP3WmskRxGa6irFnKxkspJqUpQN1mFselj6iQrzAcwkRPoCw0RwCCMq1/OOYvQtgxTJcO4zDVlbw54PvnxPZtcCWw7fO8oZ2Fvo2SDo75CDOATOGaT4Y9iqECgYEAzWZSpFbN9ZHmvq1lJQg//jFAyjsXRNn/nSvyLQILXltz6EHatImnXo3v+SivG91tfzBI1GfDvGUGaJpvKHoomB+qmhd8KIQhO5MBdAKZMf9fZqZofOPTD9xRXECCwdi+XqHBmL+l1OWz+O9Bh+Qobs2as/hQVgHaoXhQpE0NkTcCgYEA/Tjf6JBGl1+WxQDoGZDJrXoejzG9OFW19RjMdmPrg3t4fnbDtqTpZtCzXxPTCSeMrvplKbqAqZglWyq227ksKw4p7O6YfyhdtvC58oJmivlLr6sFaTsER7mDcYce8sQpqm+XQ8IPbnOk0Z1l6g56euTwTnew49uy25M6U1xL0P8CgYEAxEXv2Kw+OVhHV5PX4BBHHj6we88FiDyMfwM8cvfOJ0datekf9X7ImZkmZEAVPJpWBMD+B0J0jzU2b4SLjfFVkzBHVOH2Ob0xCH2MWPAWtekin7OKizUlPbW5ZV8b0+Kq30DQ/4a7D3rEhK8UPqeuX1tHZox1MAqrgbq3zJj4yvcCgYEAktYPKPm4pYCdmgFrlZ+bA0iEPf7Wvbsd91F5BtHsOOM5PQQ7e0bnvWIaEXEad/2CG9lBHlBy2WVLjDEZthILpa/h6e11ao8KwNGY0iKBuebT17rxOVMqqTjPGt8CuD2994IcEgOPFTpkAdUmyvG4XlkxbB8F6St17NPUB5DGuhsCgYA//Lfytk0FflXEeRQ16LT1YXgV7pcR2jsha4+4O5pxSFw/kTsOfJaYHg8StmROoyFnyE3sg76dCgLn0LENRCe5BvDhJnp5bMpQldG3XwcAxH8FGFNY4LtV/2ZKnJhxcONkfmzQPOmTyedOzrKQ+bNURsqLukCypP7/by6afBY4dA=="}}`
|
||||||
|
const baseRSAx509Key = `{"keytype":"{{.KeyType}}","keyval":{"public":"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZLekNDQXhXZ0F3SUJBZ0lRSGZoeWdIbWFkenNMRW9vR0tUbzNuekFMQmdrcWhraUc5dzBCQVFzd09ERWEKTUJnR0ExVUVDaE1SWkc5amEyVnlMbU52YlM5dWIzUmhjbmt4R2pBWUJnTlZCQU1URVdSdlkydGxjaTVqYjIwdgpibTkwWVhKNU1CNFhEVEUxTURjeE16QTBNell4TTFvWERURTNNRGN4TWpBME16WXhNMW93T0RFYU1CZ0dBMVVFCkNoTVJaRzlqYTJWeUxtTnZiUzl1YjNSaGNua3hHakFZQmdOVkJBTVRFV1J2WTJ0bGNpNWpiMjB2Ym05MFlYSjUKTUlJQ0lqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FnOEFNSUlDQ2dLQ0FnRUFuVUZoelBSeUgyOG90SWRJSnlEdApXZDBMcURqQkZMUXNxZXRiTC90QS9hdmxVNE1UQk44eFBJQmJrazNjWDU2bTdOQVBwWDBaZkUzMGc3UXBkVElNCjJteUpNMUtLN2lnQkJzd3czMkpUOVhHRW15K0lWb1Nwc1lCdzJkMWF5dGdxWUI4UXZhZ01zamc4eEc2aWVhUGwKcG9tcUVYdEt1YzBoOTEyaTQ4YURpUzlIK3ExMmlvcmlkVDRmazFrcm1sZ1orMHMrSlZobUFlQ0FiMmZvTFc5YworUDErUnlEQ3FZN2NyaXhZcUJ3c3ZIZ00zbUw4SitmWlZVUWZLYTVmQlA1dFp5MGk3UE9QVFZpdVl3R20rSHlYCmhyQnRpalF0b0R3Y1U4VEVEdDAyelJSd0N3elZKMFhwdGhrVmRqZUNrSkFFcGpyOHVGQ1ZKYlJXOWgrWXRLQlYKMCtzMWl5elFqVWwydklEczRiSVc0RzVaeVp0OHNSaTAzRFhHTnNtNDhrRWlaVWswd0RuNGpzMW8vdUJEVUN6YwphdHdrN2t1aVhrcFFNMVdkRmF6TCtmYWJueWR3Z285bWI2c1FKQlRxMDdvNEI0M0JWYTBHZm5ZSFRsVUtWSHZ6CmNwb1pNWTMyb1AyN0t5TXlybkxETzducUlBQnA1UEFvMUpNU09GWWdKa3R1Sk5LT2h0Sm9qcUgyV21wajRvbzkKQmZMY2d6TFNQd2ZTbytXS0FaVmQzYU1FcnFCQ3RBcVN2aUdmdVRaT3FkK2JKZGY4aW1jZ3ZCeWdacVVRb0J2aAo4Q1hSWGxUNTdKSUFrVkY3aUxrVUZoUkhxY2lwVjZqVzFWeEFXVzJiZ0xrMEhzTnpRQkN2NjQ2YzkwU2d3cGZvCmxLTEJPNFE0QUdsaFFQUmxNQUNPMFRFQ0F3RUFBYU0xTURNd0RnWURWUjBQQVFIL0JBUURBZ0NnTUJNR0ExVWQKSlFRTU1Bb0dDQ3NHQVFVRkJ3TURNQXdHQTFVZEV3RUIvd1FDTUFBd0N3WUpLb1pJaHZjTkFRRUxBNElDQVFCbQo4QWU2RWw5WHlNWHlyRzN0Vkd3clZBZWFYUkNiTFllNDh2b3d1QTA2Ykx1VTh0L0dXcVBRMHhZVFBtRzdsdS9qCjJNalVIeXphZ2hpVUNOdWFvNDhDbGwyemJEajlHZkMvQWJKQUFybGRHc2lReWMwbDY1QUJJaHo5aml1dXlXQ0YKMnBsWFc4RCtldlQxSm5RanRiUXB4c2Q0Um1UOC9NRjVnK29mN0RJU0dGekFIQkNicFFjbTJWRytIZ3NSOEFGcgp6VTg4YU1uakJSNm9CN0IvU0tuaytHNDFrczZLWVJqcmNCS2tBMjlIYUVNUVk5eVNEN2pYUmdJb1pqY2FMR3hlCjAyYldnZTJ2d2hGRkZoYVhaZCtDSWFVWXhvcEVBM3ZCUzlTS1N3UFNQNEpuWDFCZU1KRS8zWElIUVFXdFZuREoKL05YbnFxUTJCNkF1azhMZGRsREpQSDRiNnpZMmdzNmVvVlFRU2FSdUEyd1Q2bkY4WHVIa2dEcUttQ2E4WHVMTgo5bFV0Y0dBeHc0WitUVXlSK2lyRVQwWk14TkNwU01zcUJieGtwU29DaFd2ekgyQTMrMklmSXhielNxWnZoaVF3Ck5zVlpSZTVWNVBSQlE4TVZ3L0FBUE96V0hzWjJCZEw4UXNFQ3Y5dDBlWWxEb3BwMlp5K3RSMkM1SDFQYTg4Y0kKbFFycEs4NGlhVnRYN1ZLek1nZ3hJK0ZsczZaRVR6WnlnT1dvZ0JKMUp5MnJsZ0Z6eFFRYks5S2dCWnl4RnkvZQp3VEVDdW1SSExPN0RucmR2ZU1LY1ZnVTlsaGViQ2ZaNlZiWERUSWFYcGZXYVZSYmpnS1ZwanJSdnZPZTZHVUsyClN3S005dG4wcGRIM09iczV3RzlSZ3pTUkxSUFByMU9TalhTSTI1UGlpUT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K","private":null}}`
|
||||||
|
const baseECDSAKey = `
|
||||||
|
{"keytype":"{{.KeyType}}","keyval":{"public":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEgl3rzMPMEKhS1k/AX16MM4PdidpjJr+z4pj0Td+30QnpbOIARgpyR1PiFztU8BZlqG3cUazvFclr2q/xHvfrqw==","private":"MHcCAQEEIDqtcdzU7H3AbIPSQaxHl9+xYECt7NpK7B1+6ep5cv9CoAoGCCqGSM49AwEHoUQDQgAEgl3rzMPMEKhS1k/AX16MM4PdidpjJr+z4pj0Td+30QnpbOIARgpyR1PiFztU8BZlqG3cUazvFclr2q/xHvfrqw=="}}`
|
||||||
|
const baseECDSAx509Key = `{"keytype":"ecdsa-x509","keyval":{"public":"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJwRENDQVVtZ0F3SUJBZ0lRQlBWc1NoUmRMdG45WEtRZ29JaDIvREFLQmdncWhrak9QUVFEQWpBNE1Sb3cKR0FZRFZRUUtFeEZrYjJOclpYSXVZMjl0TDI1dmRHRnllVEVhTUJnR0ExVUVBeE1SWkc5amEyVnlMbU52YlM5dQpiM1JoY25rd0hoY05NVFV3TnpFek1EVXdORFF4V2hjTk1UY3dOekV5TURVd05EUXhXakE0TVJvd0dBWURWUVFLCkV4RmtiMk5yWlhJdVkyOXRMMjV2ZEdGeWVURWFNQmdHQTFVRUF4TVJaRzlqYTJWeUxtTnZiUzl1YjNSaGNua3cKV1RBVEJnY3Foa2pPUFFJQkJnZ3Foa2pPUFFNQkJ3TkNBQVI3SjNSOGpWODV5Rnp0dGFTV3FMRDFHa042UHlhWAowUUdmOHh2Rzd6MUYwUG5DQUdSWk9QQ01aWWpZSGVkdzNXY0FmQWVVcDY5OVExSjNEYW9kbzNBcm96VXdNekFPCkJnTlZIUThCQWY4RUJBTUNBS0F3RXdZRFZSMGxCQXd3Q2dZSUt3WUJCUVVIQXdNd0RBWURWUjBUQVFIL0JBSXcKQURBS0JnZ3Foa2pPUFFRREFnTkpBREJHQWlFQWppVkJjaTBDRTBaazgwZ2ZqbytYdE9xM3NURGJkSWJRRTZBTQpoL29mN1RFQ0lRRGxlbXB5MDRhY0RKODNnVHBvaFNtcFJYdjdJbnRLc0lRTU1oLy9VZzliU2c9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==","private":null}}`
|
||||||
|
|
||||||
|
func TestRSAVerifier(t *testing.T) {
|
||||||
|
// Unmarshal our private RSA Key
|
||||||
|
var testRSAKey data.PrivateKey
|
||||||
|
var jsonKey bytes.Buffer
|
||||||
|
|
||||||
|
// Execute our template
|
||||||
|
templ, _ := template.New("KeyTemplate").Parse(baseRSAKey)
|
||||||
|
templ.Execute(&jsonKey, KeyTemplate{KeyType: data.RSAKey})
|
||||||
|
|
||||||
|
json.Unmarshal(jsonKey.Bytes(), &testRSAKey)
|
||||||
|
|
||||||
|
// Sign some data using RSAPSS
|
||||||
|
message := []byte("test data for signing")
|
||||||
|
hash := crypto.SHA256
|
||||||
|
hashed := sha256.Sum256(message)
|
||||||
|
signedData, err := rsaSign(&testRSAKey, hash, hashed[:])
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Create and call Verify on the verifier
|
||||||
|
rsaVerifier := RSAPSSVerifier{}
|
||||||
|
err = rsaVerifier.Verify(&testRSAKey, signedData, message)
|
||||||
|
assert.NoError(t, err, "expecting success but got error while verifying data using RSA PSS")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRSAx509Verifier(t *testing.T) {
|
||||||
|
// Unmarshal our private RSA Key
|
||||||
|
var testRSAKey data.PublicKey
|
||||||
|
var jsonKey bytes.Buffer
|
||||||
|
|
||||||
|
// Execute our template
|
||||||
|
templ, _ := template.New("KeyTemplate").Parse(baseRSAx509Key)
|
||||||
|
templ.Execute(&jsonKey, KeyTemplate{KeyType: data.RSAx509Key})
|
||||||
|
|
||||||
|
json.Unmarshal(jsonKey.Bytes(), &testRSAKey)
|
||||||
|
|
||||||
|
// Valid signed message
|
||||||
|
signedData, _ := hex.DecodeString("9a196a3458e0a9077772c1b3cf6f1605ac69576711d55a89ba390d68723a135aa851cf7a574074ae35fa5c22b0f8e28d31ab05ef66c96456707be2bfa3487edd4531996593bd3f0dd2a6d2034bf4adc1828f5502240a1c4a70506e2b218419d2498487725c22917455617c659087de2a6cb73023bd40dfa868c7e70f1e22e86a4c588f97294f0da1ba537c20a6f06692c6de34c305d3be0bfbeaabb712531d9b52e3118f252c87b27467587b457ae906f73183bec68ae2b56fda41757193b0b7f97fe27cf9efb6be101cad2edd014f5862df6b8fdcd939504f846624349bc480ef3b074b69d5096796c480bf8c6e41b95c2aefa54c6c34d22742c93e82e6dd42080a8d9841057130306f194b07b60c9cb54e5a16b1755f5a1180ab86c2bb244f17c9ccc9c326debacc35dc14a4d8226d75e7cd40b9843e7eacc138d59406d1a5e5f907c8bea588346441f2c464f74e18a0c063bd3ee27ec475929929dd248bcb2972812dc7ce3ab1513bc445f00a43fb98321cae75da6bf8f07ac4f26dd782db57338aa97350814eea55f160ba5c6c893d064edaf8f31d98d2fb544f0b54b5b4e30786dca9f8ef8ea4fa3d1a07335ae2a252079f1ffcadc8f9c53b8c8e32e0e4f9677ef781dba894a49442008d209d3a9b89a03f1ecb191fb1e56f4b894e2c073fe41d41d06a8261804e7321feb095d6da97b4c41aee4180718ee0d9bd964a4e")
|
||||||
|
message := []byte("test data for signing")
|
||||||
|
|
||||||
|
// Create and call Verify on the verifier
|
||||||
|
rsaVerifier := RSAPSSVerifier{}
|
||||||
|
err := rsaVerifier.Verify(&testRSAKey, signedData, message)
|
||||||
|
assert.NoError(t, err, "expecting success but got error while verifying data using RSAPSS and an X509 encoded Key")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRSAVerifierWithInvalidKeyType(t *testing.T) {
|
||||||
|
var testRSAKey data.PrivateKey
|
||||||
|
var jsonKey bytes.Buffer
|
||||||
|
|
||||||
|
// Execute our template
|
||||||
|
templ, _ := template.New("KeyTemplate").Parse(baseRSAKey)
|
||||||
|
templ.Execute(&jsonKey, KeyTemplate{KeyType: "rsa-invalid"})
|
||||||
|
|
||||||
|
json.Unmarshal(jsonKey.Bytes(), &testRSAKey)
|
||||||
|
|
||||||
|
// Valid signed data with invalidRsaKeyJSON
|
||||||
|
signedData, _ := hex.DecodeString("2741a57a5ef89f841b4e0a6afbcd7940bc982cd919fbd11dfc21b5ccfe13855b9c401e3df22da5480cef2fa585d0f6dfc6c35592ed92a2a18001362c3a17f74da3906684f9d81c5846bf6a09e2ede6c009ae164f504e6184e666adb14eadf5f6e12e07ff9af9ad49bf1ea9bcfa3bebb2e33be7d4c0fabfe39534f98f1e3c4bff44f637cff3dae8288aea54d86476a3f1320adc39008eae24b991c1de20744a7967d2e685ac0bcc0bc725947f01c9192ffd3e9300eba4b7faa826e84478493fdf97c705dd331dd46072050d6c5e317c2d63df21694dbaf909ebf46ce0ff04f3979fe13723ae1a823c65f27e56efa19e88f9e7b8ee56eac34353b944067deded3a")
|
||||||
|
message := []byte("test data for signing")
|
||||||
|
|
||||||
|
// Create and call Verify on the verifier
|
||||||
|
rsaVerifier := RSAPSSVerifier{}
|
||||||
|
err := rsaVerifier.Verify(&testRSAKey, signedData, message)
|
||||||
|
assert.Error(t, err, "invalid key type for RSAPSS verifier: rsa-invalid")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRSAVerifierWithInvalidKey(t *testing.T) {
|
||||||
|
var testRSAKey data.PrivateKey
|
||||||
|
var jsonKey bytes.Buffer
|
||||||
|
|
||||||
|
// Execute our template
|
||||||
|
templ, _ := template.New("KeyTemplate").Parse(baseECDSAKey)
|
||||||
|
templ.Execute(&jsonKey, KeyTemplate{KeyType: "ecdsa"})
|
||||||
|
|
||||||
|
json.Unmarshal(jsonKey.Bytes(), &testRSAKey)
|
||||||
|
|
||||||
|
// Valid signed data with invalidRsaKeyJSON
|
||||||
|
signedData, _ := hex.DecodeString("2741a57a5ef89f841b4e0a6afbcd7940bc982cd919fbd11dfc21b5ccfe13855b9c401e3df22da5480cef2fa585d0f6dfc6c35592ed92a2a18001362c3a17f74da3906684f9d81c5846bf6a09e2ede6c009ae164f504e6184e666adb14eadf5f6e12e07ff9af9ad49bf1ea9bcfa3bebb2e33be7d4c0fabfe39534f98f1e3c4bff44f637cff3dae8288aea54d86476a3f1320adc39008eae24b991c1de20744a7967d2e685ac0bcc0bc725947f01c9192ffd3e9300eba4b7faa826e84478493fdf97c705dd331dd46072050d6c5e317c2d63df21694dbaf909ebf46ce0ff04f3979fe13723ae1a823c65f27e56efa19e88f9e7b8ee56eac34353b944067deded3a")
|
||||||
|
message := []byte("test data for signing")
|
||||||
|
|
||||||
|
// Create and call Verify on the verifier
|
||||||
|
rsaVerifier := RSAPSSVerifier{}
|
||||||
|
err := rsaVerifier.Verify(&testRSAKey, signedData, message)
|
||||||
|
assert.Error(t, err, "invalid key type for RSAPSS verifier: ecdsa")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRSAVerifierWithInvalidSignature(t *testing.T) {
|
||||||
|
var testRSAKey data.PrivateKey
|
||||||
|
var jsonKey bytes.Buffer
|
||||||
|
|
||||||
|
// Execute our template
|
||||||
|
templ, _ := template.New("KeyTemplate").Parse(baseRSAKey)
|
||||||
|
templ.Execute(&jsonKey, KeyTemplate{KeyType: data.RSAKey})
|
||||||
|
|
||||||
|
json.Unmarshal(jsonKey.Bytes(), &testRSAKey)
|
||||||
|
|
||||||
|
// Sign some data using RSAPSS
|
||||||
|
message := []byte("test data for signing")
|
||||||
|
hash := crypto.SHA256
|
||||||
|
hashed := sha256.Sum256(message)
|
||||||
|
signedData, err := rsaSign(&testRSAKey, hash, hashed[:])
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Modify the signature
|
||||||
|
signedData[0] = []byte("a")[0]
|
||||||
|
|
||||||
|
// Create and call Verify on the verifier
|
||||||
|
rsaVerifier := RSAPSSVerifier{}
|
||||||
|
err = rsaVerifier.Verify(&testRSAKey, signedData, message)
|
||||||
|
assert.Error(t, err, "signature verification failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestECDSAVerifier(t *testing.T) {
|
||||||
|
var testECDSAKey data.PrivateKey
|
||||||
|
var jsonKey bytes.Buffer
|
||||||
|
|
||||||
|
// Execute our template
|
||||||
|
templ, _ := template.New("KeyTemplate").Parse(baseECDSAKey)
|
||||||
|
templ.Execute(&jsonKey, KeyTemplate{KeyType: data.ECDSAKey})
|
||||||
|
|
||||||
|
json.Unmarshal(jsonKey.Bytes(), &testECDSAKey)
|
||||||
|
|
||||||
|
// Sign some data using ECDSA
|
||||||
|
message := []byte("test data for signing")
|
||||||
|
hashed := sha256.Sum256(message)
|
||||||
|
signedData, err := ecdsaSign(&testECDSAKey, hashed[:])
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Create and call Verify on the verifier
|
||||||
|
ecdsaVerifier := ECDSAVerifier{}
|
||||||
|
err = ecdsaVerifier.Verify(&testECDSAKey, signedData, message)
|
||||||
|
assert.NoError(t, err, "expecting success but got error while verifying data using ECDSA")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestECDSAx509Verifier(t *testing.T) {
|
||||||
|
var testECDSAKey data.PrivateKey
|
||||||
|
var jsonKey bytes.Buffer
|
||||||
|
|
||||||
|
// Execute our template
|
||||||
|
templ, _ := template.New("KeyTemplate").Parse(baseECDSAx509Key)
|
||||||
|
templ.Execute(&jsonKey, KeyTemplate{KeyType: data.ECDSAx509Key})
|
||||||
|
|
||||||
|
json.Unmarshal(jsonKey.Bytes(), &testECDSAKey)
|
||||||
|
|
||||||
|
// Valid signature for message
|
||||||
|
signedData, _ := hex.DecodeString("b82e0ed5c5dddd74c8d3602bfd900c423511697c3cfe54e1d56b9c1df599695c53aa0caafcdc40df3ef496d78ccf67750ba9413f1ccbd8b0ef137f0da1ee9889")
|
||||||
|
message := []byte("test data for signing")
|
||||||
|
|
||||||
|
// Create and call Verify on the verifier
|
||||||
|
ecdsaVerifier := ECDSAVerifier{}
|
||||||
|
err := ecdsaVerifier.Verify(&testECDSAKey, signedData, message)
|
||||||
|
assert.NoError(t, err, "expecting success but got error while verifying data using ECDSA and an x509 encoded key")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestECDSAVerifierWithInvalidKeyType(t *testing.T) {
|
||||||
|
var testECDSAKey data.PrivateKey
|
||||||
|
var jsonKey bytes.Buffer
|
||||||
|
|
||||||
|
// Execute our template
|
||||||
|
templ, _ := template.New("KeyTemplate").Parse(baseECDSAKey)
|
||||||
|
templ.Execute(&jsonKey, KeyTemplate{KeyType: "ecdsa-invalid"})
|
||||||
|
|
||||||
|
json.Unmarshal(jsonKey.Bytes(), &testECDSAKey)
|
||||||
|
|
||||||
|
// Valid signature using invalidECDSAx509Key
|
||||||
|
signedData, _ := hex.DecodeString("7b1c45a4dd488a087db46ee459192d890d4f52352620cb84c2c10e0ce8a67fd6826936463a91ffdffab8e6f962da6fc3d3e5735412f7cd161a9fcf97ba1a7033")
|
||||||
|
message := []byte("test data for signing")
|
||||||
|
|
||||||
|
// Create and call Verify on the verifier
|
||||||
|
ecdsaVerifier := ECDSAVerifier{}
|
||||||
|
err := ecdsaVerifier.Verify(&testECDSAKey, signedData, message)
|
||||||
|
assert.Error(t, err, "invalid key type for ECDSA verifier: ecdsa-invalid")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestECDSAVerifierWithInvalidKey(t *testing.T) {
|
||||||
|
var testECDSAKey data.PrivateKey
|
||||||
|
var jsonKey bytes.Buffer
|
||||||
|
|
||||||
|
// Execute our template
|
||||||
|
templ, _ := template.New("KeyTemplate").Parse(baseRSAKey)
|
||||||
|
templ.Execute(&jsonKey, KeyTemplate{KeyType: "rsa"})
|
||||||
|
|
||||||
|
json.Unmarshal(jsonKey.Bytes(), &testECDSAKey)
|
||||||
|
|
||||||
|
// Valid signature using invalidECDSAx509Key
|
||||||
|
signedData, _ := hex.DecodeString("7b1c45a4dd488a087db46ee459192d890d4f52352620cb84c2c10e0ce8a67fd6826936463a91ffdffab8e6f962da6fc3d3e5735412f7cd161a9fcf97ba1a7033")
|
||||||
|
message := []byte("test data for signing")
|
||||||
|
|
||||||
|
// Create and call Verify on the verifier
|
||||||
|
ecdsaVerifier := ECDSAVerifier{}
|
||||||
|
err := ecdsaVerifier.Verify(&testECDSAKey, signedData, message)
|
||||||
|
assert.Error(t, err, "invalid key type for ECDSA verifier: rsa")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestECDSAVerifierWithInvalidSignature(t *testing.T) {
|
||||||
|
var testECDSAKey data.PrivateKey
|
||||||
|
var jsonKey bytes.Buffer
|
||||||
|
|
||||||
|
// Execute our template
|
||||||
|
templ, _ := template.New("KeyTemplate").Parse(baseECDSAKey)
|
||||||
|
templ.Execute(&jsonKey, KeyTemplate{KeyType: data.ECDSAKey})
|
||||||
|
|
||||||
|
json.Unmarshal(jsonKey.Bytes(), &testECDSAKey)
|
||||||
|
|
||||||
|
// Sign some data using ECDSA
|
||||||
|
message := []byte("test data for signing")
|
||||||
|
hashed := sha256.Sum256(message)
|
||||||
|
signedData, err := ecdsaSign(&testECDSAKey, hashed[:])
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Modify the signature
|
||||||
|
signedData[0] = []byte("a")[0]
|
||||||
|
|
||||||
|
// Create and call Verify on the verifier
|
||||||
|
ecdsaVerifier := ECDSAVerifier{}
|
||||||
|
err = ecdsaVerifier.Verify(&testECDSAKey, signedData, message)
|
||||||
|
assert.Error(t, err, "signature verification failed")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func rsaSign(privKey *data.PrivateKey, hash crypto.Hash, hashed []byte) ([]byte, error) {
|
||||||
|
if privKey.Cipher() != data.RSAKey {
|
||||||
|
return nil, fmt.Errorf("private key type not supported: %s", privKey.Cipher())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create an rsa.PrivateKey out of the private key bytes
|
||||||
|
rsaPrivKey, err := x509.ParsePKCS1PrivateKey(privKey.Private())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the RSA key to RSASSA-PSS sign the data
|
||||||
|
sig, err := rsa.SignPSS(rand.Reader, rsaPrivKey, hash, hashed[:], &rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthEqualsHash})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return sig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ecdsaSign(privKey *data.PrivateKey, hashed []byte) ([]byte, error) {
|
||||||
|
if privKey.Cipher() != data.ECDSAKey {
|
||||||
|
return nil, fmt.Errorf("private key type not supported: %s", privKey.Cipher())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create an ecdsa.PrivateKey out of the private key bytes
|
||||||
|
ecdsaPrivKey, err := x509.ParseECPrivateKey(privKey.Private())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the ECDSA key to sign the data
|
||||||
|
r, s, err := ecdsa.Sign(rand.Reader, ecdsaPrivKey, hashed[:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rBytes, sBytes := r.Bytes(), s.Bytes()
|
||||||
|
octetLength := (ecdsaPrivKey.Params().BitSize + 7) >> 3
|
||||||
|
|
||||||
|
// MUST include leading zeros in the output
|
||||||
|
rBuf := make([]byte, octetLength-len(rBytes), octetLength)
|
||||||
|
sBuf := make([]byte, octetLength-len(sBytes), octetLength)
|
||||||
|
|
||||||
|
rBuf = append(rBuf, rBytes...)
|
||||||
|
sBuf = append(sBuf, sBytes...)
|
||||||
|
|
||||||
|
return append(rBuf, sBuf...), nil
|
||||||
|
}
|
||||||
|
|
|
@ -49,7 +49,7 @@ func VerifyRoot(s *data.Signed, minVersion int, keys map[string]*data.PublicKey,
|
||||||
method := strings.ToLower(sig.Method)
|
method := strings.ToLower(sig.Method)
|
||||||
verifier, ok := Verifiers[method]
|
verifier, ok := Verifiers[method]
|
||||||
if !ok {
|
if !ok {
|
||||||
logrus.Debugf("continuing b/c signing method is not supported: %s\n", sig.Method)
|
logrus.Debugf("continuing b/c signing method is not supported for verify root: %s\n", sig.Method)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,11 +78,6 @@ func verifyMeta(s *data.Signed, role string, minVersion int) error {
|
||||||
if err := json.Unmarshal(s.Signed, sm); err != nil {
|
if err := json.Unmarshal(s.Signed, sm); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// This is not the valid way to check types as all targets files will
|
|
||||||
// have the "Targets" type.
|
|
||||||
//if strings.ToLower(sm.Type) != strings.ToLower(role) {
|
|
||||||
// return ErrWrongType
|
|
||||||
//}
|
|
||||||
if !data.ValidTUFType(sm.Type) {
|
if !data.ValidTUFType(sm.Type) {
|
||||||
return ErrWrongType
|
return ErrWrongType
|
||||||
}
|
}
|
||||||
|
|
|
@ -260,9 +260,8 @@ func (tr *TufRepo) SetRoot(s *data.Signed) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for kid, key := range r.Signed.Keys {
|
for _, key := range r.Signed.Keys {
|
||||||
tr.keysDB.AddKey(key)
|
tr.keysDB.AddKey(key)
|
||||||
logrus.Debug("Given Key ID:", kid, "\nGenerated Key ID:", key.ID())
|
|
||||||
}
|
}
|
||||||
for roleName, role := range r.Signed.Roles {
|
for roleName, role := range r.Signed.Roles {
|
||||||
baseRole, err := data.NewRole(
|
baseRole, err := data.NewRole(
|
||||||
|
@ -451,7 +450,7 @@ func (tr *TufRepo) UpdateTimestamp(s *data.Signed) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tr *TufRepo) SignRoot(expires time.Time, signer *signed.Signer) (*data.Signed, error) {
|
func (tr *TufRepo) SignRoot(expires time.Time, signer *signed.Signer) (*data.Signed, error) {
|
||||||
logrus.Debug("SignRoot")
|
logrus.Debug("signing root...")
|
||||||
if tr.Root.Dirty {
|
if tr.Root.Dirty {
|
||||||
tr.Root.Signed.Version++
|
tr.Root.Signed.Version++
|
||||||
}
|
}
|
||||||
|
@ -469,8 +468,7 @@ func (tr *TufRepo) SignRoot(expires time.Time, signer *signed.Signer) (*data.Sig
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tr *TufRepo) SignTargets(role string, expires time.Time, signer *signed.Signer) (*data.Signed, error) {
|
func (tr *TufRepo) SignTargets(role string, expires time.Time, signer *signed.Signer) (*data.Signed, error) {
|
||||||
logrus.Debug("SignTargets")
|
logrus.Debugf("sign targets called for role %s", role)
|
||||||
logrus.Debug("Got targets data.Signed object")
|
|
||||||
if tr.Targets[role].Dirty {
|
if tr.Targets[role].Dirty {
|
||||||
tr.Targets[role].Signed.Version++
|
tr.Targets[role].Signed.Version++
|
||||||
signed, err := tr.Targets[role].ToSigned()
|
signed, err := tr.Targets[role].ToSigned()
|
||||||
|
@ -479,13 +477,11 @@ func (tr *TufRepo) SignTargets(role string, expires time.Time, signer *signed.Si
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
targets := tr.keysDB.GetRole(role)
|
targets := tr.keysDB.GetRole(role)
|
||||||
logrus.Debug("About to sign ", role)
|
|
||||||
signed, err = tr.sign(signed, *targets, signer)
|
signed, err = tr.sign(signed, *targets, signer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Debug("errored signing ", role)
|
logrus.Debug("errored signing ", role)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
logrus.Debug("success signing ", role)
|
|
||||||
tr.Targets[role].Signatures = signed.Signatures
|
tr.Targets[role].Signatures = signed.Signatures
|
||||||
return signed, nil
|
return signed, nil
|
||||||
} else {
|
} else {
|
||||||
|
@ -499,7 +495,7 @@ func (tr *TufRepo) SignTargets(role string, expires time.Time, signer *signed.Si
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tr *TufRepo) SignSnapshot(expires time.Time, signer *signed.Signer) (*data.Signed, error) {
|
func (tr *TufRepo) SignSnapshot(expires time.Time, signer *signed.Signer) (*data.Signed, error) {
|
||||||
logrus.Debug("SignSnapshot")
|
logrus.Debug("signing snapshot...")
|
||||||
if tr.Root.Dirty {
|
if tr.Root.Dirty {
|
||||||
signedRoot, err := tr.SignRoot(data.DefaultExpires("root"), signer)
|
signedRoot, err := tr.SignRoot(data.DefaultExpires("root"), signer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -2,92 +2,111 @@ package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto"
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/notary/trustmanager"
|
"github.com/docker/notary/trustmanager"
|
||||||
"github.com/endophage/gotuf/data"
|
"github.com/endophage/gotuf/data"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CryptoService implements Sign and Create, holding a specific GUN and keystore to
|
type genericCryptoService struct {
|
||||||
// operate on
|
|
||||||
type CryptoService struct {
|
|
||||||
gun string
|
gun string
|
||||||
passphrase string
|
passphrase string
|
||||||
keyStore *trustmanager.KeyFileStore
|
keyStore *trustmanager.KeyFileStore
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCryptoService returns an instance of CryptoService
|
// RSACryptoService implements Sign and Create, holding a specific GUN and keystore to
|
||||||
func NewCryptoService(gun string, keyStore *trustmanager.KeyFileStore) *CryptoService {
|
// operate on
|
||||||
return &CryptoService{gun: gun, keyStore: keyStore}
|
type RSACryptoService struct {
|
||||||
|
genericCryptoService
|
||||||
|
}
|
||||||
|
|
||||||
|
// ECDSACryptoService implements Sign and Create, holding a specific GUN and keystore to
|
||||||
|
// operate on
|
||||||
|
type ECDSACryptoService struct {
|
||||||
|
genericCryptoService
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRSACryptoService returns an instance of CryptoService
|
||||||
|
func NewRSACryptoService(gun string, keyStore *trustmanager.KeyFileStore, passphrase string) *RSACryptoService {
|
||||||
|
return &RSACryptoService{genericCryptoService{gun: gun, keyStore: keyStore, passphrase: passphrase}}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create is used to generate keys for targets, snapshots and timestamps
|
// Create is used to generate keys for targets, snapshots and timestamps
|
||||||
func (ccs *CryptoService) Create(role string) (*data.PublicKey, error) {
|
func (ccs *RSACryptoService) Create(role string) (*data.PublicKey, error) {
|
||||||
privKey, err := trustmanager.GenerateRSAKey(rand.Reader, rsaKeySize)
|
privKey, err := trustmanager.GenerateRSAKey(rand.Reader, rsaKeySize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to generate RSA key: %v", err)
|
return nil, fmt.Errorf("failed to generate RSA key: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store the private key into our keystore with the name being: /GUN/ID.key
|
// Store the private key into our keystore with the name being: /GUN/ID.key
|
||||||
ccs.keyStore.AddKey(filepath.Join(ccs.gun, privKey.ID()), privKey)
|
err = ccs.keyStore.AddKey(filepath.Join(ccs.gun, privKey.ID()), privKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to add key to filestore: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.Debugf("generated new RSA key for role: %s and keyID: %s", role, privKey.ID())
|
||||||
|
|
||||||
return data.PublicKeyFromPrivate(*privKey), nil
|
return data.PublicKeyFromPrivate(*privKey), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetPassphrase tells the cryptoservice the passphrase. Use only if the key needs
|
// Sign returns the signatures for the payload with a set of keyIDs. It ignores
|
||||||
// to be decrypted.
|
// errors to sign and expects the called to validate if the number of returned
|
||||||
func (ccs *CryptoService) SetPassphrase(passphrase string) {
|
// signatures is adequate.
|
||||||
ccs.passphrase = passphrase
|
func (ccs *RSACryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signature, error) {
|
||||||
}
|
|
||||||
|
|
||||||
// Sign returns the signatures for data with the given root Key ID, falling back
|
|
||||||
// if not rootKeyID is found
|
|
||||||
func (ccs *CryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signature, error) {
|
|
||||||
// Create hasher and hash data
|
// Create hasher and hash data
|
||||||
hash := crypto.SHA256
|
hash := crypto.SHA256
|
||||||
hashed := sha256.Sum256(payload)
|
hashed := sha256.Sum256(payload)
|
||||||
|
|
||||||
signatures := make([]data.Signature, 0, len(keyIDs))
|
signatures := make([]data.Signature, 0, len(keyIDs))
|
||||||
for _, fingerprint := range keyIDs {
|
for _, keyid := range keyIDs {
|
||||||
// ccs.gun will be empty if this is the root key
|
// ccs.gun will be empty if this is the root key
|
||||||
keyName := filepath.Join(ccs.gun, fingerprint)
|
keyName := filepath.Join(ccs.gun, keyid)
|
||||||
|
|
||||||
var privKey *data.PrivateKey
|
var privKey *data.PrivateKey
|
||||||
var err error
|
var err error
|
||||||
var method string
|
|
||||||
|
|
||||||
// Read PrivateKey from file
|
// Read PrivateKey from file.
|
||||||
|
// TODO(diogo): This assumes both that only root keys are encrypted and
|
||||||
|
// that encrypted keys are always X509 encoded
|
||||||
if ccs.passphrase != "" {
|
if ccs.passphrase != "" {
|
||||||
// This is a root key
|
// This is a root key
|
||||||
privKey, err = ccs.keyStore.GetDecryptedKey(keyName, ccs.passphrase)
|
privKey, err = ccs.keyStore.GetDecryptedKey(keyName, ccs.passphrase)
|
||||||
method = "RSASSA-PSS-X509"
|
|
||||||
} else {
|
} else {
|
||||||
privKey, err = ccs.keyStore.GetKey(keyName)
|
privKey, err = ccs.keyStore.GetKey(keyName)
|
||||||
method = "RSASSA-PSS"
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Note that GetDecryptedKey always fails on InitRepo.
|
// Note that GetDecryptedKey always fails on InitRepo.
|
||||||
// InitRepo gets a signer that doesn't have access to
|
// InitRepo gets a signer that doesn't have access to
|
||||||
// the root keys. Continuing here is safe because we
|
// the root keys. Continuing here is safe because we
|
||||||
// end up not returning any signatures.
|
// end up not returning any signatures.
|
||||||
|
logrus.Debugf("ignoring error attempting to retrieve RSA key ID: %s, %v", keyid, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
sig, err := sign(privKey, hash, hashed[:])
|
sig, err := rsaSign(privKey, hash, hashed[:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
// If the rsaSign method got called with a non RSA private key,
|
||||||
|
// we ignore this call.
|
||||||
|
// This might happen when root is ECDSA, targets and snapshots RSA, and
|
||||||
|
// gotuf still attempts to sign root with this cryptoserver
|
||||||
|
// return nil, err
|
||||||
|
logrus.Debugf("ignoring error attempting to RSA sign with keyID: %s, %v", keyid, err)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logrus.Debugf("appending RSA signature with Key ID: %s", privKey.ID())
|
||||||
|
|
||||||
// Append signatures to result array
|
// Append signatures to result array
|
||||||
signatures = append(signatures, data.Signature{
|
signatures = append(signatures, data.Signature{
|
||||||
KeyID: fingerprint,
|
KeyID: keyid,
|
||||||
Method: method,
|
Method: data.RSAPSSSignature,
|
||||||
Signature: sig[:],
|
Signature: sig[:],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -95,9 +114,8 @@ func (ccs *CryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signatur
|
||||||
return signatures, nil
|
return signatures, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func sign(privKey *data.PrivateKey, hash crypto.Hash, hashed []byte) ([]byte, error) {
|
func rsaSign(privKey *data.PrivateKey, hash crypto.Hash, hashed []byte) ([]byte, error) {
|
||||||
// TODO(diogo): Implement support for ECDSA.
|
if privKey.Cipher() != data.RSAKey {
|
||||||
if strings.ToLower(privKey.Cipher()) != "rsa" {
|
|
||||||
return nil, fmt.Errorf("private key type not supported: %s", privKey.Cipher())
|
return nil, fmt.Errorf("private key type not supported: %s", privKey.Cipher())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,7 +125,7 @@ func sign(privKey *data.PrivateKey, hash crypto.Hash, hashed []byte) ([]byte, er
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use the RSA key to sign the data
|
// Use the RSA key to RSASSA-PSS sign the data
|
||||||
sig, err := rsa.SignPSS(rand.Reader, rsaPrivKey, hash, hashed[:], &rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthEqualsHash})
|
sig, err := rsa.SignPSS(rand.Reader, rsaPrivKey, hash, hashed[:], &rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthEqualsHash})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -115,3 +133,118 @@ func sign(privKey *data.PrivateKey, hash crypto.Hash, hashed []byte) ([]byte, er
|
||||||
|
|
||||||
return sig, nil
|
return sig, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewECDSACryptoService returns an instance of CryptoService
|
||||||
|
func NewECDSACryptoService(gun string, keyStore *trustmanager.KeyFileStore, passphrase string) *ECDSACryptoService {
|
||||||
|
return &ECDSACryptoService{genericCryptoService{gun: gun, keyStore: keyStore, passphrase: passphrase}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create is used to generate keys for targets, snapshots and timestamps
|
||||||
|
func (ccs *ECDSACryptoService) Create(role string) (*data.PublicKey, error) {
|
||||||
|
privKey, err := trustmanager.GenerateECDSAKey(rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to generate EC key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the private key into our keystore with the name being: /GUN/ID.key
|
||||||
|
err = ccs.keyStore.AddKey(filepath.Join(ccs.gun, privKey.ID()), privKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to add key to filestore: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.Debugf("generated new ECDSA key for role %s with keyID: %s", role, privKey.ID())
|
||||||
|
|
||||||
|
return data.PublicKeyFromPrivate(*privKey), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign returns the signatures for the payload with a set of keyIDs. It ignores
|
||||||
|
// errors to sign and expects the called to validate if the number of returned
|
||||||
|
// signatures is adequate.
|
||||||
|
func (ccs *ECDSACryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signature, error) {
|
||||||
|
// Create hasher and hash data
|
||||||
|
hashed := sha256.Sum256(payload)
|
||||||
|
|
||||||
|
signatures := make([]data.Signature, 0, len(keyIDs))
|
||||||
|
for _, keyid := range keyIDs {
|
||||||
|
// ccs.gun will be empty if this is the root key
|
||||||
|
keyName := filepath.Join(ccs.gun, keyid)
|
||||||
|
|
||||||
|
var privKey *data.PrivateKey
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// Read PrivateKey from file
|
||||||
|
// TODO(diogo): This assumes both that only root keys are encrypted and
|
||||||
|
// that encrypted keys are always X509 encoded
|
||||||
|
if ccs.passphrase != "" {
|
||||||
|
// This is a root key
|
||||||
|
privKey, err = ccs.keyStore.GetDecryptedKey(keyName, ccs.passphrase)
|
||||||
|
} else {
|
||||||
|
privKey, err = ccs.keyStore.GetKey(keyName)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
// Note that GetDecryptedKey always fails on InitRepo.
|
||||||
|
// InitRepo gets a signer that doesn't have access to
|
||||||
|
// the root keys. Continuing here is safe because we
|
||||||
|
// end up not returning any signatures.
|
||||||
|
// TODO(diogo): figure out if there are any specific error types to
|
||||||
|
// check. We're swallowing all errors.
|
||||||
|
logrus.Debugf("Ignoring error attempting to retrieve ECDSA key ID: %s, %v", keyid, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("ERROR: ", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
sig, err := ecdsaSign(privKey, hashed[:])
|
||||||
|
if err != nil {
|
||||||
|
// If the ecdsaSign method got called with a non ECDSA private key,
|
||||||
|
// we ignore this call.
|
||||||
|
// This might happen when root is RSA, targets and snapshots ECDSA, and
|
||||||
|
// gotuf still attempts to sign root with this cryptoserver
|
||||||
|
// return nil, err
|
||||||
|
logrus.Debugf("ignoring error attempting to ECDSA sign with keyID: %s, %v", privKey.ID(), err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.Debugf("appending ECDSA signature with Key ID: %s", privKey.ID())
|
||||||
|
|
||||||
|
// Append signatures to result array
|
||||||
|
signatures = append(signatures, data.Signature{
|
||||||
|
KeyID: keyid,
|
||||||
|
Method: data.ECDSASignature,
|
||||||
|
Signature: sig[:],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return signatures, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ecdsaSign(privKey *data.PrivateKey, hashed []byte) ([]byte, error) {
|
||||||
|
if privKey.Cipher() != data.ECDSAKey {
|
||||||
|
return nil, fmt.Errorf("private key type not supported: %s", privKey.Cipher())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create an ecdsa.PrivateKey out of the private key bytes
|
||||||
|
ecdsaPrivKey, err := x509.ParseECPrivateKey(privKey.Private())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the ECDSA key to sign the data
|
||||||
|
r, s, err := ecdsa.Sign(rand.Reader, ecdsaPrivKey, hashed[:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rBytes, sBytes := r.Bytes(), s.Bytes()
|
||||||
|
octetLength := (ecdsaPrivKey.Params().BitSize + 7) >> 3
|
||||||
|
|
||||||
|
// MUST include leading zeros in the output
|
||||||
|
rBuf := make([]byte, octetLength-len(rBytes), octetLength)
|
||||||
|
sBuf := make([]byte, octetLength-len(sBytes), octetLength)
|
||||||
|
|
||||||
|
rBuf = append(rBuf, rBytes...)
|
||||||
|
sBuf = append(sBuf, sBytes...)
|
||||||
|
|
||||||
|
return append(rBuf, sBuf...), nil
|
||||||
|
}
|
||||||
|
|
115
client/client.go
115
client/client.go
|
@ -2,7 +2,10 @@ package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
|
@ -12,6 +15,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
|
@ -111,8 +115,8 @@ func NewNotaryRepository(baseDir, gun, baseURL string, rt http.RoundTripper) (*N
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
logrus.Debugf("creating non-root cryptoservice")
|
// TODO(diogo): This hardcodes snapshots and targets to using EC. Change it.
|
||||||
signer := signed.NewSigner(NewCryptoService(gun, privKeyStore))
|
signer := signed.NewSigner(NewECDSACryptoService(gun, privKeyStore, ""))
|
||||||
|
|
||||||
nRepo := &NotaryRepository{
|
nRepo := &NotaryRepository{
|
||||||
gun: gun,
|
gun: gun,
|
||||||
|
@ -139,12 +143,36 @@ func (r *NotaryRepository) Initialize(uSigner *UnlockedSigner) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
r.certificateStore.AddCert(rootCert)
|
r.certificateStore.AddCert(rootCert)
|
||||||
rootKey := data.NewPublicKey("RSA", trustmanager.CertToPEM(rootCert))
|
|
||||||
|
// The root key gets stored in the TUF metadata X509 encoded, linking
|
||||||
|
// the tuf root.json to our X509 PKI.
|
||||||
|
// If the key is RSA, we store it as type RSAx509, if it is ECDSA we store it
|
||||||
|
// as ECDSAx509 to allow the gotuf verifiers to correctly decode the
|
||||||
|
// key on verification of signatures.
|
||||||
|
var cipherType string
|
||||||
|
cipher := uSigner.privKey.Cipher()
|
||||||
|
switch cipher {
|
||||||
|
case data.RSAKey:
|
||||||
|
cipherType = data.RSAx509Key
|
||||||
|
case data.ECDSAKey:
|
||||||
|
cipherType = data.ECDSAx509Key
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("invalid format for root key: %s", cipher)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a x509Key using the rootCert as the public key
|
||||||
|
rootKey := data.NewPublicKey(cipherType, trustmanager.CertToPEM(rootCert))
|
||||||
|
|
||||||
|
// Creates a symlink between the certificate ID and the real public key it
|
||||||
|
// is associated with. This is used to be able to retrieve the root private key
|
||||||
|
// associated with a particular certificate
|
||||||
|
logrus.Debugf("Linking %s to %s.", rootKey.ID(), uSigner.ID())
|
||||||
err = r.rootKeyStore.Link(uSigner.ID(), rootKey.ID())
|
err = r.rootKeyStore.Link(uSigner.ID(), rootKey.ID())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// All the timestamp keys are generated by the remote server.
|
||||||
remote, err := getRemoteStore(r.baseURL, r.gun, r.roundTrip)
|
remote, err := getRemoteStore(r.baseURL, r.gun, r.roundTrip)
|
||||||
rawTSKey, err := remote.GetKey("timestamp")
|
rawTSKey, err := remote.GetKey("timestamp")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -157,8 +185,11 @@ func (r *NotaryRepository) Initialize(uSigner *UnlockedSigner) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Turn the JSON timestamp key from the remote server into a TUFKey
|
||||||
timestampKey := data.NewPublicKey(parsedKey.Cipher(), parsedKey.Public())
|
timestampKey := data.NewPublicKey(parsedKey.Cipher(), parsedKey.Public())
|
||||||
|
logrus.Debugf("got remote %s timestamp key with keyID: %s", parsedKey.Cipher(), timestampKey.ID())
|
||||||
|
|
||||||
|
// Targets and snapshot keys are always generated locally.
|
||||||
targetsKey, err := r.signer.Create("targets")
|
targetsKey, err := r.signer.Create("targets")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -534,29 +565,32 @@ func (r *NotaryRepository) validateRoot(root *data.Signed) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
certs := make(map[string]*data.PublicKey)
|
certs := make(map[string]*data.PublicKey)
|
||||||
for _, fingerprint := range rootSigned.Roles["root"].KeyIDs {
|
for _, keyID := range rootSigned.Roles["root"].KeyIDs {
|
||||||
// TODO(dlaw): currently assuming only one cert contained in
|
// TODO(dlaw): currently assuming only one cert contained in
|
||||||
// public key entry. Need to fix when we want to pass in chains.
|
// public key entry. Need to fix when we want to pass in chains.
|
||||||
k, _ := pem.Decode([]byte(rootSigned.Keys[fingerprint].Public()))
|
k, _ := pem.Decode([]byte(rootSigned.Keys[keyID].Public()))
|
||||||
logrus.Debug("Root PEM: ", k)
|
|
||||||
logrus.Debug("Root ID: ", fingerprint)
|
|
||||||
decodedCerts, err := x509.ParseCertificates(k.Bytes)
|
decodedCerts, err := x509.ParseCertificates(k.Bytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
logrus.Debugf("error while parsing root certificate with keyID: %s, %v", keyID, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// TODO(diogo): Assuming that first certificate is the leaf-cert. Need to
|
// TODO(diogo): Assuming that first certificate is the leaf-cert. Need to
|
||||||
// iterate over all decodedCerts and find a non-CA one (should be the last).
|
// iterate over all decodedCerts and find a non-CA one (should be the last).
|
||||||
leafCert := decodedCerts[0]
|
leafCert := decodedCerts[0]
|
||||||
|
|
||||||
leafID := trustmanager.FingerprintCert(leafCert)
|
leafID, err := trustmanager.FingerprintCert(leafCert)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debugf("error while fingerprinting root certificate with keyID: %s, %v", keyID, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// Check to see if there is an exact match of this certificate.
|
// Check to see if there is an exact match of this certificate.
|
||||||
// Checking the CommonName is not required since ID is calculated over
|
// Checking the CommonName is not required since ID is calculated over
|
||||||
// Cert.Raw. It's included to prevent breaking logic with changes of how the
|
// Cert.Raw. It's included to prevent breaking logic with changes of how the
|
||||||
// ID gets computed.
|
// ID gets computed.
|
||||||
_, err = r.certificateStore.GetCertificateByFingerprint(leafID)
|
_, err = r.certificateStore.GetCertificateByKeyID(leafID)
|
||||||
if err == nil && leafCert.Subject.CommonName == r.gun {
|
if err == nil && leafCert.Subject.CommonName == r.gun {
|
||||||
certs[fingerprint] = rootSigned.Keys[fingerprint]
|
certs[keyID] = rootSigned.Keys[keyID]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check to see if this leafCertificate has a chain to one of the Root CAs
|
// Check to see if this leafCertificate has a chain to one of the Root CAs
|
||||||
|
@ -564,7 +598,7 @@ func (r *NotaryRepository) validateRoot(root *data.Signed) error {
|
||||||
certList := []*x509.Certificate{leafCert}
|
certList := []*x509.Certificate{leafCert}
|
||||||
err = trustmanager.Verify(r.caStore, r.gun, certList)
|
err = trustmanager.Verify(r.caStore, r.gun, certList)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
certs[fingerprint] = rootSigned.Keys[fingerprint]
|
certs[keyID] = rootSigned.Keys[keyID]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -619,12 +653,24 @@ func (r *NotaryRepository) ListRootKeys() []string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GenRootKey generates a new root key protected by a given passphrase
|
// GenRootKey generates a new root key protected by a given passphrase
|
||||||
func (r *NotaryRepository) GenRootKey(passphrase string) (string, error) {
|
func (r *NotaryRepository) GenRootKey(algorithm, passphrase string) (string, error) {
|
||||||
privKey, err := trustmanager.GenerateRSAKey(rand.Reader, rsaRootKeySize)
|
var err error
|
||||||
|
var privKey *data.PrivateKey
|
||||||
|
|
||||||
|
switch strings.ToLower(algorithm) {
|
||||||
|
case data.RSAKey:
|
||||||
|
privKey, err = trustmanager.GenerateRSAKey(rand.Reader, rsaRootKeySize)
|
||||||
|
case data.ECDSAKey:
|
||||||
|
privKey, err = trustmanager.GenerateECDSAKey(rand.Reader)
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("only RSA or ECDSA keys are currently supported. Found: %s", algorithm)
|
||||||
|
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to convert private key: %v", err)
|
return "", fmt.Errorf("failed to generate private key: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Changing the root
|
||||||
r.rootKeyStore.AddEncryptedKey(privKey.ID(), privKey, passphrase)
|
r.rootKeyStore.AddEncryptedKey(privKey.ID(), privKey, passphrase)
|
||||||
|
|
||||||
return privKey.ID(), nil
|
return privKey.ID(), nil
|
||||||
|
@ -634,16 +680,20 @@ func (r *NotaryRepository) GenRootKey(passphrase string) (string, error) {
|
||||||
func (r *NotaryRepository) GetRootSigner(rootKeyID, passphrase string) (*UnlockedSigner, error) {
|
func (r *NotaryRepository) GetRootSigner(rootKeyID, passphrase string) (*UnlockedSigner, error) {
|
||||||
privKey, err := r.rootKeyStore.GetDecryptedKey(rootKeyID, passphrase)
|
privKey, err := r.rootKeyStore.GetDecryptedKey(rootKeyID, passphrase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not get decrypted root key: %v", err)
|
return nil, fmt.Errorf("could not get decrypted root key with keyID: %s, %v", rootKeyID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// This signer will be used for all of the normal TUF operations, except for
|
var signer *signed.Signer
|
||||||
// when a root key is needed.
|
cipher := privKey.Cipher()
|
||||||
|
|
||||||
// Passing an empty GUN because root keys aren't associated with a GUN.
|
// Passing an empty GUN because root keys aren't associated with a GUN.
|
||||||
ccs := NewCryptoService("", r.rootKeyStore)
|
switch strings.ToLower(cipher) {
|
||||||
ccs.SetPassphrase(passphrase)
|
case data.RSAKey:
|
||||||
signer := signed.NewSigner(ccs)
|
signer = signed.NewSigner(NewRSACryptoService("", r.rootKeyStore, passphrase))
|
||||||
|
case data.ECDSAKey:
|
||||||
|
signer = signed.NewSigner(NewECDSACryptoService("", r.rootKeyStore, passphrase))
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("only RSA or ECDSA keys are currently supported. Found: %s", cipher)
|
||||||
|
}
|
||||||
|
|
||||||
return &UnlockedSigner{
|
return &UnlockedSigner{
|
||||||
privKey: privKey,
|
privKey: privKey,
|
||||||
|
@ -700,9 +750,26 @@ func (uk *UnlockedSigner) PublicKey() *data.PublicKey {
|
||||||
|
|
||||||
// GenerateCertificate generates an X509 Certificate from a template, given a GUN
|
// GenerateCertificate generates an X509 Certificate from a template, given a GUN
|
||||||
func (uk *UnlockedSigner) GenerateCertificate(gun string) (*x509.Certificate, error) {
|
func (uk *UnlockedSigner) GenerateCertificate(gun string) (*x509.Certificate, error) {
|
||||||
privKey, err := x509.ParsePKCS1PrivateKey(uk.privKey.Private())
|
cipher := uk.privKey.Cipher()
|
||||||
|
var publicKey crypto.PublicKey
|
||||||
|
var privateKey crypto.PrivateKey
|
||||||
|
var err error
|
||||||
|
switch cipher {
|
||||||
|
case data.RSAKey:
|
||||||
|
var rsaPrivateKey *rsa.PrivateKey
|
||||||
|
rsaPrivateKey, err = x509.ParsePKCS1PrivateKey(uk.privKey.Private())
|
||||||
|
privateKey = rsaPrivateKey
|
||||||
|
publicKey = rsaPrivateKey.Public()
|
||||||
|
case data.ECDSAKey:
|
||||||
|
var ecdsaPrivateKey *ecdsa.PrivateKey
|
||||||
|
ecdsaPrivateKey, err = x509.ParseECPrivateKey(uk.privKey.Private())
|
||||||
|
privateKey = ecdsaPrivateKey
|
||||||
|
publicKey = ecdsaPrivateKey.Public()
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("only RSA or ECDSA keys are currently supported. Found: %s", cipher)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to parse root key: %v (%s)", gun, err.Error())
|
return nil, fmt.Errorf("failed to parse root key: %s (%v)", gun, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
template, err := trustmanager.NewCertificate(gun)
|
template, err := trustmanager.NewCertificate(gun)
|
||||||
|
@ -710,7 +777,7 @@ func (uk *UnlockedSigner) GenerateCertificate(gun string) (*x509.Certificate, er
|
||||||
return nil, fmt.Errorf("failed to create the certificate template for: %s (%v)", gun, err)
|
return nil, fmt.Errorf("failed to create the certificate template for: %s (%v)", gun, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
derBytes, err := x509.CreateCertificate(rand.Reader, template, template, privKey.Public(), privKey)
|
derBytes, err := x509.CreateCertificate(rand.Reader, template, template, publicKey, privateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create the certificate for: %s (%v)", gun, err)
|
return nil, fmt.Errorf("failed to create the certificate for: %s (%v)", gun, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,12 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
const timestampKeyJSON = `{"keytype":"RSA","keyval":{"public":"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyyvBtTg2xzYS+MTTIBqSpI4V78tt8Yzqi7Jki/Z6NqjiDvcnbgcTqNR2t6B2W5NjGdp/hSaT2jyHM+kdmEGaPxg/zIuHbL3NIp4e0qwovWiEgACPIaELdn8O/kt5swsSKl1KMvLCH1sM86qMibNMAZ/hXOwd90TcHXCgZ91wHEAmsdjDC3dB0TT+FBgOac8RM01Y196QrZoOaDMTWh0EQfw7YbXAElhFVDFxBzDdYWbcIHSIogXQmq0CP+zaL/1WgcZZIClt2M6WCaxxF1S34wNn45gCvVZiZQ/iKWHerSr/2dGQeGo+7ezMSutRzvJ+01fInD86RS/CEtBCFZ1VyQIDAQAB","private":"MIIEpAIBAAKCAQEAyyvBtTg2xzYS+MTTIBqSpI4V78tt8Yzqi7Jki/Z6NqjiDvcnbgcTqNR2t6B2W5NjGdp/hSaT2jyHM+kdmEGaPxg/zIuHbL3NIp4e0qwovWiEgACPIaELdn8O/kt5swsSKl1KMvLCH1sM86qMibNMAZ/hXOwd90TcHXCgZ91wHEAmsdjDC3dB0TT+FBgOac8RM01Y196QrZoOaDMTWh0EQfw7YbXAElhFVDFxBzDdYWbcIHSIogXQmq0CP+zaL/1WgcZZIClt2M6WCaxxF1S34wNn45gCvVZiZQ/iKWHerSr/2dGQeGo+7ezMSutRzvJ+01fInD86RS/CEtBCFZ1VyQIDAQABAoIBAHar8FFxrE1gAGTeUpOF8fG8LIQMRwO4U6eVY7V9GpWiv6gOJTHXYFxU/aL0Ty3eQRxwy9tyVRo8EJz5pRex+e6ws1M+jLOviYqW4VocxQ8dZYd+zBvQfWmRfah7XXJ/HPUx2I05zrmR7VbGX6Bu4g5w3KnyIO61gfyQNKF2bm2Q3yblfupx3URvX0bl180R/+QN2Aslr4zxULFE6b+qJqBydrztq+AAP3WmskRxGa6irFnKxkspJqUpQN1mFselj6iQrzAcwkRPoCw0RwCCMq1/OOYvQtgxTJcO4zDVlbw54PvnxPZtcCWw7fO8oZ2Fvo2SDo75CDOATOGaT4Y9iqECgYEAzWZSpFbN9ZHmvq1lJQg//jFAyjsXRNn/nSvyLQILXltz6EHatImnXo3v+SivG91tfzBI1GfDvGUGaJpvKHoomB+qmhd8KIQhO5MBdAKZMf9fZqZofOPTD9xRXECCwdi+XqHBmL+l1OWz+O9Bh+Qobs2as/hQVgHaoXhQpE0NkTcCgYEA/Tjf6JBGl1+WxQDoGZDJrXoejzG9OFW19RjMdmPrg3t4fnbDtqTpZtCzXxPTCSeMrvplKbqAqZglWyq227ksKw4p7O6YfyhdtvC58oJmivlLr6sFaTsER7mDcYce8sQpqm+XQ8IPbnOk0Z1l6g56euTwTnew49uy25M6U1xL0P8CgYEAxEXv2Kw+OVhHV5PX4BBHHj6we88FiDyMfwM8cvfOJ0datekf9X7ImZkmZEAVPJpWBMD+B0J0jzU2b4SLjfFVkzBHVOH2Ob0xCH2MWPAWtekin7OKizUlPbW5ZV8b0+Kq30DQ/4a7D3rEhK8UPqeuX1tHZox1MAqrgbq3zJj4yvcCgYEAktYPKPm4pYCdmgFrlZ+bA0iEPf7Wvbsd91F5BtHsOOM5PQQ7e0bnvWIaEXEad/2CG9lBHlBy2WVLjDEZthILpa/h6e11ao8KwNGY0iKBuebT17rxOVMqqTjPGt8CuD2994IcEgOPFTpkAdUmyvG4XlkxbB8F6St17NPUB5DGuhsCgYA//Lfytk0FflXEeRQ16LT1YXgV7pcR2jsha4+4O5pxSFw/kTsOfJaYHg8StmROoyFnyE3sg76dCgLn0LENRCe5BvDhJnp5bMpQldG3XwcAxH8FGFNY4LtV/2ZKnJhxcONkfmzQPOmTyedOzrKQ+bNURsqLukCypP7/by6afBY4dA=="}}`
|
// TODO(diogo): timestamps have to be the same keytype as targets and snapshots.
|
||||||
|
// Can't test an RSA timestamp key until we stop hardcoding ECDSA as the default
|
||||||
|
// CryptoService in NewNotaryRepository
|
||||||
|
const timestampKeyJSON = `{"keytype":"rsa","keyval":{"public":"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyyvBtTg2xzYS+MTTIBqSpI4V78tt8Yzqi7Jki/Z6NqjiDvcnbgcTqNR2t6B2W5NjGdp/hSaT2jyHM+kdmEGaPxg/zIuHbL3NIp4e0qwovWiEgACPIaELdn8O/kt5swsSKl1KMvLCH1sM86qMibNMAZ/hXOwd90TcHXCgZ91wHEAmsdjDC3dB0TT+FBgOac8RM01Y196QrZoOaDMTWh0EQfw7YbXAElhFVDFxBzDdYWbcIHSIogXQmq0CP+zaL/1WgcZZIClt2M6WCaxxF1S34wNn45gCvVZiZQ/iKWHerSr/2dGQeGo+7ezMSutRzvJ+01fInD86RS/CEtBCFZ1VyQIDAQAB","private":"MIIEpAIBAAKCAQEAyyvBtTg2xzYS+MTTIBqSpI4V78tt8Yzqi7Jki/Z6NqjiDvcnbgcTqNR2t6B2W5NjGdp/hSaT2jyHM+kdmEGaPxg/zIuHbL3NIp4e0qwovWiEgACPIaELdn8O/kt5swsSKl1KMvLCH1sM86qMibNMAZ/hXOwd90TcHXCgZ91wHEAmsdjDC3dB0TT+FBgOac8RM01Y196QrZoOaDMTWh0EQfw7YbXAElhFVDFxBzDdYWbcIHSIogXQmq0CP+zaL/1WgcZZIClt2M6WCaxxF1S34wNn45gCvVZiZQ/iKWHerSr/2dGQeGo+7ezMSutRzvJ+01fInD86RS/CEtBCFZ1VyQIDAQABAoIBAHar8FFxrE1gAGTeUpOF8fG8LIQMRwO4U6eVY7V9GpWiv6gOJTHXYFxU/aL0Ty3eQRxwy9tyVRo8EJz5pRex+e6ws1M+jLOviYqW4VocxQ8dZYd+zBvQfWmRfah7XXJ/HPUx2I05zrmR7VbGX6Bu4g5w3KnyIO61gfyQNKF2bm2Q3yblfupx3URvX0bl180R/+QN2Aslr4zxULFE6b+qJqBydrztq+AAP3WmskRxGa6irFnKxkspJqUpQN1mFselj6iQrzAcwkRPoCw0RwCCMq1/OOYvQtgxTJcO4zDVlbw54PvnxPZtcCWw7fO8oZ2Fvo2SDo75CDOATOGaT4Y9iqECgYEAzWZSpFbN9ZHmvq1lJQg//jFAyjsXRNn/nSvyLQILXltz6EHatImnXo3v+SivG91tfzBI1GfDvGUGaJpvKHoomB+qmhd8KIQhO5MBdAKZMf9fZqZofOPTD9xRXECCwdi+XqHBmL+l1OWz+O9Bh+Qobs2as/hQVgHaoXhQpE0NkTcCgYEA/Tjf6JBGl1+WxQDoGZDJrXoejzG9OFW19RjMdmPrg3t4fnbDtqTpZtCzXxPTCSeMrvplKbqAqZglWyq227ksKw4p7O6YfyhdtvC58oJmivlLr6sFaTsER7mDcYce8sQpqm+XQ8IPbnOk0Z1l6g56euTwTnew49uy25M6U1xL0P8CgYEAxEXv2Kw+OVhHV5PX4BBHHj6we88FiDyMfwM8cvfOJ0datekf9X7ImZkmZEAVPJpWBMD+B0J0jzU2b4SLjfFVkzBHVOH2Ob0xCH2MWPAWtekin7OKizUlPbW5ZV8b0+Kq30DQ/4a7D3rEhK8UPqeuX1tHZox1MAqrgbq3zJj4yvcCgYEAktYPKPm4pYCdmgFrlZ+bA0iEPf7Wvbsd91F5BtHsOOM5PQQ7e0bnvWIaEXEad/2CG9lBHlBy2WVLjDEZthILpa/h6e11ao8KwNGY0iKBuebT17rxOVMqqTjPGt8CuD2994IcEgOPFTpkAdUmyvG4XlkxbB8F6St17NPUB5DGuhsCgYA//Lfytk0FflXEeRQ16LT1YXgV7pcR2jsha4+4O5pxSFw/kTsOfJaYHg8StmROoyFnyE3sg76dCgLn0LENRCe5BvDhJnp5bMpQldG3XwcAxH8FGFNY4LtV/2ZKnJhxcONkfmzQPOmTyedOzrKQ+bNURsqLukCypP7/by6afBY4dA=="}}`
|
||||||
|
const timestampECDSAKeyJSON = `
|
||||||
|
{"keytype":"ecdsa","keyval":{"public":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEgl3rzMPMEKhS1k/AX16MM4PdidpjJr+z4pj0Td+30QnpbOIARgpyR1PiFztU8BZlqG3cUazvFclr2q/xHvfrqw==","private":"MHcCAQEEIDqtcdzU7H3AbIPSQaxHl9+xYECt7NpK7B1+6ep5cv9CoAoGCCqGSM49AwEHoUQDQgAEgl3rzMPMEKhS1k/AX16MM4PdidpjJr+z4pj0Td+30QnpbOIARgpyR1PiFztU8BZlqG3cUazvFclr2q/xHvfrqw=="}}`
|
||||||
|
|
||||||
func createTestServer(t *testing.T) (*httptest.Server, *http.ServeMux) {
|
func createTestServer(t *testing.T) (*httptest.Server, *http.ServeMux) {
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
|
@ -26,7 +31,7 @@ func createTestServer(t *testing.T) (*httptest.Server, *http.ServeMux) {
|
||||||
mux.HandleFunc("/v2/docker.com/notary/_trust/tuf/timestamp.key", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/v2/docker.com/notary/_trust/tuf/timestamp.key", func(w http.ResponseWriter, r *http.Request) {
|
||||||
// Also contains the private key, but for the purpose of this
|
// Also contains the private key, but for the purpose of this
|
||||||
// test, we don't care
|
// test, we don't care
|
||||||
fmt.Fprint(w, timestampKeyJSON)
|
fmt.Fprint(w, timestampECDSAKeyJSON)
|
||||||
})
|
})
|
||||||
|
|
||||||
ts := httptest.NewServer(mux)
|
ts := httptest.NewServer(mux)
|
||||||
|
@ -36,7 +41,13 @@ func createTestServer(t *testing.T) (*httptest.Server, *http.ServeMux) {
|
||||||
|
|
||||||
// TestInitRepo runs through the process of initializing a repository and makes
|
// TestInitRepo runs through the process of initializing a repository and makes
|
||||||
// sure the repository looks correct on disk.
|
// sure the repository looks correct on disk.
|
||||||
|
// We test this with both an RSA and ECDSA root key
|
||||||
func TestInitRepo(t *testing.T) {
|
func TestInitRepo(t *testing.T) {
|
||||||
|
testInitRepo(t, data.RSAKey)
|
||||||
|
testInitRepo(t, data.ECDSAKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testInitRepo(t *testing.T, rootType string) {
|
||||||
gun := "docker.com/notary"
|
gun := "docker.com/notary"
|
||||||
// Temporary directory where test files will be created
|
// Temporary directory where test files will be created
|
||||||
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
||||||
|
@ -50,11 +61,11 @@ func TestInitRepo(t *testing.T) {
|
||||||
repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport)
|
repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport)
|
||||||
assert.NoError(t, err, "error creating repo: %s", err)
|
assert.NoError(t, err, "error creating repo: %s", err)
|
||||||
|
|
||||||
rootKeyID, err := repo.GenRootKey("passphrase")
|
rootKeyID, err := repo.GenRootKey(rootType, "passphrase")
|
||||||
assert.NoError(t, err, "error generating root key: %s", err)
|
assert.NoError(t, err, "error generating root key: %s", err)
|
||||||
|
|
||||||
rootSigner, err := repo.GetRootSigner(rootKeyID, "passphrase")
|
rootSigner, err := repo.GetRootSigner(rootKeyID, "passphrase")
|
||||||
assert.NoError(t, err, "error retreiving root key: %s", err)
|
assert.NoError(t, err, "error retrieving root key: %s", err)
|
||||||
|
|
||||||
err = repo.Initialize(rootSigner)
|
err = repo.Initialize(rootSigner)
|
||||||
assert.NoError(t, err, "error creating repository: %s", err)
|
assert.NoError(t, err, "error creating repository: %s", err)
|
||||||
|
@ -96,7 +107,8 @@ func TestInitRepo(t *testing.T) {
|
||||||
certificates := repo.certificateStore.GetCertificates()
|
certificates := repo.certificateStore.GetCertificates()
|
||||||
assert.Len(t, certificates, 1, "unexpected number of certificates")
|
assert.Len(t, certificates, 1, "unexpected number of certificates")
|
||||||
|
|
||||||
certID := trustmanager.FingerprintCert(certificates[0])
|
certID, err := trustmanager.FingerprintCert(certificates[0])
|
||||||
|
assert.NoError(t, err, "unable to fingerprint the certificate")
|
||||||
|
|
||||||
actualDest, err := os.Readlink(filepath.Join(tempBaseDir, "private", "root_keys", certID+".key"))
|
actualDest, err := os.Readlink(filepath.Join(tempBaseDir, "private", "root_keys", certID+".key"))
|
||||||
assert.NoError(t, err, "missing symlink to root key")
|
assert.NoError(t, err, "missing symlink to root key")
|
||||||
|
@ -170,7 +182,13 @@ type tufChange struct {
|
||||||
// is updated correctly. Then it calls ListTargets and checks the return value.
|
// is updated correctly. Then it calls ListTargets and checks the return value.
|
||||||
// Using ListTargets involves serving signed metadata files over the test's
|
// Using ListTargets involves serving signed metadata files over the test's
|
||||||
// internal HTTP server.
|
// internal HTTP server.
|
||||||
|
// We test this with both an RSA and ECDSA root key
|
||||||
func TestAddListTarget(t *testing.T) {
|
func TestAddListTarget(t *testing.T) {
|
||||||
|
testAddListTarget(t, data.RSAKey)
|
||||||
|
testAddListTarget(t, data.ECDSAKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAddListTarget(t *testing.T, rootType string) {
|
||||||
// Temporary directory where test files will be created
|
// Temporary directory where test files will be created
|
||||||
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
||||||
defer os.RemoveAll(tempBaseDir)
|
defer os.RemoveAll(tempBaseDir)
|
||||||
|
@ -185,7 +203,7 @@ func TestAddListTarget(t *testing.T) {
|
||||||
repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport)
|
repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport)
|
||||||
assert.NoError(t, err, "error creating repository: %s", err)
|
assert.NoError(t, err, "error creating repository: %s", err)
|
||||||
|
|
||||||
rootKeyID, err := repo.GenRootKey("passphrase")
|
rootKeyID, err := repo.GenRootKey(rootType, "passphrase")
|
||||||
assert.NoError(t, err, "error generating root key: %s", err)
|
assert.NoError(t, err, "error generating root key: %s", err)
|
||||||
|
|
||||||
rootSigner, err := repo.GetRootSigner(rootKeyID, "passphrase")
|
rootSigner, err := repo.GetRootSigner(rootKeyID, "passphrase")
|
||||||
|
@ -285,7 +303,7 @@ func TestAddListTarget(t *testing.T) {
|
||||||
assert.NoError(t, err, "could not apply changelist")
|
assert.NoError(t, err, "could not apply changelist")
|
||||||
|
|
||||||
var tempKey data.PrivateKey
|
var tempKey data.PrivateKey
|
||||||
json.Unmarshal([]byte(timestampKeyJSON), &tempKey)
|
json.Unmarshal([]byte(timestampECDSAKeyJSON), &tempKey)
|
||||||
|
|
||||||
repo.privKeyStore.AddKey(filepath.Join(gun, tempKey.ID()), &tempKey)
|
repo.privKeyStore.AddKey(filepath.Join(gun, tempKey.ID()), &tempKey)
|
||||||
|
|
||||||
|
@ -349,6 +367,11 @@ func TestAddListTarget(t *testing.T) {
|
||||||
// TestValidateRootKey verifies that the public data in root.json for the root
|
// TestValidateRootKey verifies that the public data in root.json for the root
|
||||||
// key is a valid x509 certificate.
|
// key is a valid x509 certificate.
|
||||||
func TestValidateRootKey(t *testing.T) {
|
func TestValidateRootKey(t *testing.T) {
|
||||||
|
testValidateRootKey(t, data.RSAKey)
|
||||||
|
testValidateRootKey(t, data.ECDSAKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testValidateRootKey(t *testing.T, rootType string) {
|
||||||
// Temporary directory where test files will be created
|
// Temporary directory where test files will be created
|
||||||
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
||||||
defer os.RemoveAll(tempBaseDir)
|
defer os.RemoveAll(tempBaseDir)
|
||||||
|
@ -363,7 +386,7 @@ func TestValidateRootKey(t *testing.T) {
|
||||||
repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport)
|
repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport)
|
||||||
assert.NoError(t, err, "error creating repository: %s", err)
|
assert.NoError(t, err, "error creating repository: %s", err)
|
||||||
|
|
||||||
rootKeyID, err := repo.GenRootKey("passphrase")
|
rootKeyID, err := repo.GenRootKey(rootType, "passphrase")
|
||||||
assert.NoError(t, err, "error generating root key: %s", err)
|
assert.NoError(t, err, "error generating root key: %s", err)
|
||||||
|
|
||||||
rootSigner, err := repo.GetRootSigner(rootKeyID, "passphrase")
|
rootSigner, err := repo.GetRootSigner(rootKeyID, "passphrase")
|
||||||
|
|
|
@ -64,7 +64,7 @@ func keysRemove(cmd *cobra.Command, args []string) {
|
||||||
gunOrID := args[0]
|
gunOrID := args[0]
|
||||||
|
|
||||||
// Try to retrieve the ID from the CA store.
|
// Try to retrieve the ID from the CA store.
|
||||||
cert, err := caStore.GetCertificateByFingerprint(gunOrID)
|
cert, err := caStore.GetCertificateByKeyID(gunOrID)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
fmt.Printf("Removing: ")
|
fmt.Printf("Removing: ")
|
||||||
printCert(cert)
|
printCert(cert)
|
||||||
|
@ -78,7 +78,7 @@ func keysRemove(cmd *cobra.Command, args []string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to retrieve the ID from the Certificate store.
|
// Try to retrieve the ID from the Certificate store.
|
||||||
cert, err = certificateStore.GetCertificateByFingerprint(gunOrID)
|
cert, err = certificateStore.GetCertificateByKeyID(gunOrID)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
fmt.Printf("Removing: ")
|
fmt.Printf("Removing: ")
|
||||||
printCert(cert)
|
printCert(cert)
|
||||||
|
@ -216,17 +216,21 @@ func keysGenerate(cmd *cobra.Command, args []string) {
|
||||||
|
|
||||||
func printCert(cert *x509.Certificate) {
|
func printCert(cert *x509.Certificate) {
|
||||||
timeDifference := cert.NotAfter.Sub(time.Now())
|
timeDifference := cert.NotAfter.Sub(time.Now())
|
||||||
fingerprint := trustmanager.FingerprintCert(cert)
|
keyID, err := trustmanager.FingerprintCert(cert)
|
||||||
fmt.Printf("%s %s (expires in: %v days)\n", cert.Subject.CommonName, fingerprint, math.Floor(timeDifference.Hours()/24))
|
if err != nil {
|
||||||
|
fatalf("could not fingerprint certificate: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%s %s (expires in: %v days)\n", cert.Subject.CommonName, keyID, math.Floor(timeDifference.Hours()/24))
|
||||||
}
|
}
|
||||||
|
|
||||||
func printKey(keyPath string) {
|
func printKey(keyPath string) {
|
||||||
keyPath = strings.TrimSuffix(keyPath, filepath.Ext(keyPath))
|
keyPath = strings.TrimSuffix(keyPath, filepath.Ext(keyPath))
|
||||||
keyPath = strings.TrimPrefix(keyPath, viper.GetString("privDir"))
|
keyPath = strings.TrimPrefix(keyPath, viper.GetString("privDir"))
|
||||||
|
|
||||||
fingerprint := filepath.Base(keyPath)
|
keyID := filepath.Base(keyPath)
|
||||||
gun := filepath.Dir(keyPath)[1:]
|
gun := filepath.Dir(keyPath)[1:]
|
||||||
fmt.Printf("%s %s\n", gun, fingerprint)
|
fmt.Printf("%s %s\n", gun, keyID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func askConfirm() bool {
|
func askConfirm() bool {
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -9,7 +11,9 @@ import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/docker/pkg/term"
|
||||||
notaryclient "github.com/docker/notary/client"
|
notaryclient "github.com/docker/notary/client"
|
||||||
|
"github.com/endophage/gotuf/data"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
@ -116,7 +120,7 @@ func tufInit(cmd *cobra.Command, args []string) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatalf(err.Error())
|
fatalf(err.Error())
|
||||||
}
|
}
|
||||||
rootKeyID, err = nRepo.GenRootKey(passphrase)
|
rootKeyID, err = nRepo.GenRootKey(data.ECDSAKey, passphrase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatalf(err.Error())
|
fatalf(err.Error())
|
||||||
}
|
}
|
||||||
|
@ -279,3 +283,43 @@ func passphraseRetriever() (string, error) {
|
||||||
}
|
}
|
||||||
return passphrase, nil
|
return passphrase, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getPassphrase(confirm bool) ([]byte, error) {
|
||||||
|
if pass := os.Getenv("NOTARY_ROOT_PASSPHRASE"); pass != "" {
|
||||||
|
return []byte(pass), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
state, err := term.SaveState(0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
term.DisableEcho(0, state)
|
||||||
|
defer term.RestoreTerminal(0, state)
|
||||||
|
|
||||||
|
stdin := bufio.NewReader(os.Stdin)
|
||||||
|
|
||||||
|
fmt.Printf("Enter root key passphrase: ")
|
||||||
|
passphrase, err := stdin.ReadBytes('\n')
|
||||||
|
fmt.Println()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
passphrase = passphrase[0 : len(passphrase)-1]
|
||||||
|
|
||||||
|
if !confirm {
|
||||||
|
return passphrase, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Repeat root key passphrase: ")
|
||||||
|
confirmation, err := stdin.ReadBytes('\n')
|
||||||
|
fmt.Println()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
confirmation = confirmation[0 : len(confirmation)-1]
|
||||||
|
|
||||||
|
if !bytes.Equal(passphrase, confirmation) {
|
||||||
|
return nil, errors.New("The entered passphrases do not match")
|
||||||
|
}
|
||||||
|
return passphrase, nil
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package storage
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/endophage/gotuf/data"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -46,23 +47,23 @@ func TestGetTimestampKey(t *testing.T) {
|
||||||
//_, _, err := s.GetTimestampKey("gun")
|
//_, _, err := s.GetTimestampKey("gun")
|
||||||
//assert.IsType(t, &ErrNoKey{}, err, "Expected err to be ErrNoKey")
|
//assert.IsType(t, &ErrNoKey{}, err, "Expected err to be ErrNoKey")
|
||||||
|
|
||||||
s.SetTimestampKey("gun", "RSA", []byte("test"))
|
s.SetTimestampKey("gun", data.RSAKey, []byte("test"))
|
||||||
|
|
||||||
c, k, err := s.GetTimestampKey("gun")
|
c, k, err := s.GetTimestampKey("gun")
|
||||||
assert.Nil(t, err, "Expected error to be nil")
|
assert.Nil(t, err, "Expected error to be nil")
|
||||||
assert.Equal(t, "RSA", c, "Expected cipher rsa, received %s", c)
|
assert.Equal(t, data.RSAKey, c, "Expected cipher rsa, received %s", c)
|
||||||
assert.Equal(t, []byte("test"), k, "Key data was wrong")
|
assert.Equal(t, []byte("test"), k, "Key data was wrong")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSetTimestampKey(t *testing.T) {
|
func TestSetTimestampKey(t *testing.T) {
|
||||||
s := NewMemStorage()
|
s := NewMemStorage()
|
||||||
s.SetTimestampKey("gun", "RSA", []byte("test"))
|
s.SetTimestampKey("gun", data.RSAKey, []byte("test"))
|
||||||
|
|
||||||
err := s.SetTimestampKey("gun", "RSA", []byte("test2"))
|
err := s.SetTimestampKey("gun", data.RSAKey, []byte("test2"))
|
||||||
assert.IsType(t, &ErrTimestampKeyExists{}, err, "Expected err to be ErrTimestampKeyExists")
|
assert.IsType(t, &ErrTimestampKeyExists{}, err, "Expected err to be ErrTimestampKeyExists")
|
||||||
|
|
||||||
k := s.tsKeys["gun"]
|
k := s.tsKeys["gun"]
|
||||||
assert.Equal(t, "RSA", k.cipher, "Expected cipher to be rsa, received %s", k.cipher)
|
assert.Equal(t, data.RSAKey, k.cipher, "Expected cipher to be rsa, received %s", k.cipher)
|
||||||
assert.Equal(t, []byte("test"), k.public, "Public key did not match expected")
|
assert.Equal(t, []byte("test"), k.public, "Public key did not match expected")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,17 +70,19 @@ func (s X509FileStore) AddCert(cert *x509.Certificate) error {
|
||||||
// addNamedCert allows adding a certificate while controling the filename it gets
|
// addNamedCert allows adding a certificate while controling the filename it gets
|
||||||
// stored under. If the file does not exist on disk, saves it.
|
// stored under. If the file does not exist on disk, saves it.
|
||||||
func (s X509FileStore) addNamedCert(cert *x509.Certificate) error {
|
func (s X509FileStore) addNamedCert(cert *x509.Certificate) error {
|
||||||
fingerprint := fingerprintCert(cert)
|
fileName, keyID, err := fileName(cert)
|
||||||
logrus.Debug("Adding cert with fingerprint: ", fingerprint)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.Debug("Adding cert with keyID: ", keyID)
|
||||||
// Validate if we already loaded this certificate before
|
// Validate if we already loaded this certificate before
|
||||||
if _, ok := s.fingerprintMap[fingerprint]; ok {
|
if _, ok := s.fingerprintMap[keyID]; ok {
|
||||||
return errors.New("certificate already in the store")
|
return errors.New("certificate already in the store")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert certificate to PEM
|
// Convert certificate to PEM
|
||||||
certBytes := CertToPEM(cert)
|
certBytes := CertToPEM(cert)
|
||||||
// Compute FileName
|
|
||||||
fileName := fileName(cert)
|
|
||||||
|
|
||||||
// Save the file to disk if not already there.
|
// Save the file to disk if not already there.
|
||||||
if _, err := os.Stat(s.fileStore.GetPath(fileName)); os.IsNotExist(err) {
|
if _, err := os.Stat(s.fileStore.GetPath(fileName)); os.IsNotExist(err) {
|
||||||
|
@ -92,11 +94,11 @@ func (s X509FileStore) addNamedCert(cert *x509.Certificate) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// We wrote the certificate succcessfully, add it to our in-memory storage
|
// We wrote the certificate succcessfully, add it to our in-memory storage
|
||||||
s.fingerprintMap[fingerprint] = cert
|
s.fingerprintMap[keyID] = cert
|
||||||
s.fileMap[fingerprint] = fileName
|
s.fileMap[keyID] = fileName
|
||||||
|
|
||||||
name := string(cert.RawSubject)
|
name := string(cert.RawSubject)
|
||||||
s.nameMap[name] = append(s.nameMap[name], fingerprint)
|
s.nameMap[name] = append(s.nameMap[name], keyID)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -107,10 +109,13 @@ func (s X509FileStore) RemoveCert(cert *x509.Certificate) error {
|
||||||
return errors.New("removing nil Certificate from X509Store")
|
return errors.New("removing nil Certificate from X509Store")
|
||||||
}
|
}
|
||||||
|
|
||||||
fingerprint := fingerprintCert(cert)
|
keyID, err := fingerprintCert(cert)
|
||||||
delete(s.fingerprintMap, fingerprint)
|
if err != nil {
|
||||||
filename := s.fileMap[fingerprint]
|
return err
|
||||||
delete(s.fileMap, fingerprint)
|
}
|
||||||
|
delete(s.fingerprintMap, keyID)
|
||||||
|
filename := s.fileMap[keyID]
|
||||||
|
delete(s.fileMap, keyID)
|
||||||
|
|
||||||
name := string(cert.RawSubject)
|
name := string(cert.RawSubject)
|
||||||
|
|
||||||
|
@ -118,7 +123,7 @@ func (s X509FileStore) RemoveCert(cert *x509.Certificate) error {
|
||||||
fpList := s.nameMap[name]
|
fpList := s.nameMap[name]
|
||||||
newfpList := fpList[:0]
|
newfpList := fpList[:0]
|
||||||
for _, x := range fpList {
|
for _, x := range fpList {
|
||||||
if x != fingerprint {
|
if x != keyID {
|
||||||
newfpList = append(newfpList, x)
|
newfpList = append(newfpList, x)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -174,15 +179,15 @@ func (s X509FileStore) GetCertificatePool() *x509.CertPool {
|
||||||
return pool
|
return pool
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCertificateByFingerprint returns the certificate that matches a certain kID or error
|
// GetCertificateByKeyID returns the certificate that matches a certain keyID or error
|
||||||
func (s X509FileStore) GetCertificateByFingerprint(hexkID string) (*x509.Certificate, error) {
|
func (s X509FileStore) GetCertificateByKeyID(keyID string) (*x509.Certificate, error) {
|
||||||
// If it does not look like a hex encoded sha256 hash, error
|
// If it does not look like a hex encoded sha256 hash, error
|
||||||
if len(hexkID) != 64 {
|
if len(keyID) != 64 {
|
||||||
return nil, errors.New("invalid Subject Key Identifier")
|
return nil, errors.New("invalid Subject Key Identifier")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check to see if this subject key identifier exists
|
// Check to see if this subject key identifier exists
|
||||||
if cert, ok := s.fingerprintMap[CertID(hexkID)]; ok {
|
if cert, ok := s.fingerprintMap[CertID(keyID)]; ok {
|
||||||
return cert, nil
|
return cert, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -207,6 +212,11 @@ func (s X509FileStore) GetVerifyOptions(dnsName string) (x509.VerifyOptions, err
|
||||||
return opts, nil
|
return opts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func fileName(cert *x509.Certificate) string {
|
func fileName(cert *x509.Certificate) (string, CertID, error) {
|
||||||
return path.Join(cert.Subject.CommonName, FingerprintCert(cert))
|
keyID, err := fingerprintCert(cert)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return path.Join(cert.Subject.CommonName, string(keyID)), keyID, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,11 +47,14 @@ func (s X509MemStore) AddCert(cert *x509.Certificate) error {
|
||||||
return errors.New("certificate failed validation")
|
return errors.New("certificate failed validation")
|
||||||
}
|
}
|
||||||
|
|
||||||
fingerprint := fingerprintCert(cert)
|
keyID, err := fingerprintCert(cert)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
s.fingerprintMap[fingerprint] = cert
|
s.fingerprintMap[keyID] = cert
|
||||||
name := string(cert.RawSubject)
|
name := string(cert.RawSubject)
|
||||||
s.nameMap[name] = append(s.nameMap[name], fingerprint)
|
s.nameMap[name] = append(s.nameMap[name], keyID)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -62,15 +65,18 @@ func (s X509MemStore) RemoveCert(cert *x509.Certificate) error {
|
||||||
return errors.New("removing nil Certificate to X509Store")
|
return errors.New("removing nil Certificate to X509Store")
|
||||||
}
|
}
|
||||||
|
|
||||||
fingerprint := fingerprintCert(cert)
|
keyID, err := fingerprintCert(cert)
|
||||||
delete(s.fingerprintMap, fingerprint)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
delete(s.fingerprintMap, keyID)
|
||||||
name := string(cert.RawSubject)
|
name := string(cert.RawSubject)
|
||||||
|
|
||||||
// Filter the fingerprint out of this name entry
|
// Filter the fingerprint out of this name entry
|
||||||
fpList := s.nameMap[name]
|
fpList := s.nameMap[name]
|
||||||
newfpList := fpList[:0]
|
newfpList := fpList[:0]
|
||||||
for _, x := range fpList {
|
for _, x := range fpList {
|
||||||
if x != fingerprint {
|
if x != keyID {
|
||||||
newfpList = append(newfpList, x)
|
newfpList = append(newfpList, x)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -139,15 +145,15 @@ func (s X509MemStore) GetCertificatePool() *x509.CertPool {
|
||||||
return pool
|
return pool
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCertificateByFingerprint returns the certificate that matches a certain kID or error
|
// GetCertificateByKeyID returns the certificate that matches a certain keyID or error
|
||||||
func (s X509MemStore) GetCertificateByFingerprint(hexkID string) (*x509.Certificate, error) {
|
func (s X509MemStore) GetCertificateByKeyID(keyID string) (*x509.Certificate, error) {
|
||||||
// If it does not look like a hex encoded sha256 hash, error
|
// If it does not look like a hex encoded sha256 hash, error
|
||||||
if len(hexkID) != 64 {
|
if len(keyID) != 64 {
|
||||||
return nil, errors.New("invalid Subject Key Identifier")
|
return nil, errors.New("invalid Subject Key Identifier")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check to see if this subject key identifier exists
|
// Check to see if this subject key identifier exists
|
||||||
if cert, ok := s.fingerprintMap[CertID(hexkID)]; ok {
|
if cert, ok := s.fingerprintMap[CertID(keyID)]; ok {
|
||||||
return cert, nil
|
return cert, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,20 +106,20 @@ func TestRemoveCert(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInexistentGetCertificateByFingerprint(t *testing.T) {
|
func TestInexistentGetCertificateByKeyID(t *testing.T) {
|
||||||
store := NewX509MemStore()
|
store := NewX509MemStore()
|
||||||
err := store.AddCertFromFile("../fixtures/notary/root-ca.crt")
|
err := store.AddCertFromFile("../fixtures/notary/root-ca.crt")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to load certificate from file: %v", err)
|
t.Fatalf("failed to load certificate from file: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = store.GetCertificateByFingerprint("4d06afd30b8bed131d2a84c97d00b37f422021598bfae34285ce98e77b708b5a")
|
_, err = store.GetCertificateByKeyID("4d06afd30b8bed131d2a84c97d00b37f422021598bfae34285ce98e77b708b5a")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("no error returned for inexistent certificate")
|
t.Fatalf("no error returned for inexistent certificate")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetCertificateByFingerprint(t *testing.T) {
|
func TestGetCertificateByKeyID(t *testing.T) {
|
||||||
b, err := ioutil.ReadFile("../fixtures/notary/root-ca.crt")
|
b, err := ioutil.ReadFile("../fixtures/notary/root-ca.crt")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("couldn't load fixture: %v", err)
|
t.Fatalf("couldn't load fixture: %v", err)
|
||||||
|
@ -138,12 +138,15 @@ func TestGetCertificateByFingerprint(t *testing.T) {
|
||||||
t.Fatalf("failed to load certificate from PEM: %v", err)
|
t.Fatalf("failed to load certificate from PEM: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
certFingerprint := FingerprintCert(cert)
|
keyID, err := FingerprintCert(cert)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to fingerprint the certificate: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Tries to retrieve cert by Subject Key IDs
|
// Tries to retrieve cert by Subject Key IDs
|
||||||
_, err = store.GetCertificateByFingerprint(certFingerprint)
|
_, err = store.GetCertificateByKeyID(keyID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("expected certificate in store: %s", certFingerprint)
|
t.Fatalf("expected certificate in store: %s", keyID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ type X509Store interface {
|
||||||
AddCertFromPEM(pemCerts []byte) error
|
AddCertFromPEM(pemCerts []byte) error
|
||||||
AddCertFromFile(filename string) error
|
AddCertFromFile(filename string) error
|
||||||
RemoveCert(cert *x509.Certificate) error
|
RemoveCert(cert *x509.Certificate) error
|
||||||
GetCertificateByFingerprint(fingerprint string) (*x509.Certificate, error)
|
GetCertificateByKeyID(keyID string) (*x509.Certificate, error)
|
||||||
GetCertificates() []*x509.Certificate
|
GetCertificates() []*x509.Certificate
|
||||||
GetCertificatePool() *x509.CertPool
|
GetCertificatePool() *x509.CertPool
|
||||||
GetVerifyOptions(dnsName string) (x509.VerifyOptions, error)
|
GetVerifyOptions(dnsName string) (x509.VerifyOptions, error)
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package trustmanager
|
package trustmanager
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
|
@ -15,6 +17,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/endophage/gotuf/data"
|
"github.com/endophage/gotuf/data"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -59,39 +62,6 @@ func CertToPEM(cert *x509.Certificate) []byte {
|
||||||
return pemCert
|
return pemCert
|
||||||
}
|
}
|
||||||
|
|
||||||
// KeyToPEM returns a PEM encoded key from a Private Key
|
|
||||||
func KeyToPEM(privKey *data.PrivateKey) ([]byte, error) {
|
|
||||||
if privKey.Cipher() != "RSA" {
|
|
||||||
return nil, errors.New("only RSA keys are currently supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
return pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: privKey.Private()}), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncryptPrivateKey returns an encrypted PEM key given a Privatekey
|
|
||||||
// and a passphrase
|
|
||||||
func EncryptPrivateKey(key *data.PrivateKey, passphrase string) ([]byte, error) {
|
|
||||||
// TODO(diogo): Currently only supports RSA Private keys
|
|
||||||
if key.Cipher() != "RSA" {
|
|
||||||
return nil, errors.New("only RSA keys are currently supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
password := []byte(passphrase)
|
|
||||||
cipherType := x509.PEMCipherAES256
|
|
||||||
blockType := "RSA PRIVATE KEY"
|
|
||||||
|
|
||||||
encryptedPEMBlock, err := x509.EncryptPEMBlock(rand.Reader,
|
|
||||||
blockType,
|
|
||||||
key.Private(),
|
|
||||||
password,
|
|
||||||
cipherType)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return pem.EncodeToMemory(encryptedPEMBlock), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadCertFromPEM returns the first certificate found in a bunch of bytes or error
|
// LoadCertFromPEM returns the first certificate found in a bunch of bytes or error
|
||||||
// if nothing is found. Taken from https://golang.org/src/crypto/x509/cert_pool.go#L85.
|
// if nothing is found. Taken from https://golang.org/src/crypto/x509/cert_pool.go#L85.
|
||||||
func LoadCertFromPEM(pemBytes []byte) (*x509.Certificate, error) {
|
func LoadCertFromPEM(pemBytes []byte) (*x509.Certificate, error) {
|
||||||
|
@ -117,18 +87,35 @@ func LoadCertFromPEM(pemBytes []byte) (*x509.Certificate, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// FingerprintCert returns a TUF compliant fingerprint for a X509 Certificate
|
// FingerprintCert returns a TUF compliant fingerprint for a X509 Certificate
|
||||||
func FingerprintCert(cert *x509.Certificate) string {
|
func FingerprintCert(cert *x509.Certificate) (string, error) {
|
||||||
return string(fingerprintCert(cert))
|
certID, err := fingerprintCert(cert)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(certID), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func fingerprintCert(cert *x509.Certificate) CertID {
|
func fingerprintCert(cert *x509.Certificate) (CertID, error) {
|
||||||
block := pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}
|
block := pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}
|
||||||
pemdata := pem.EncodeToMemory(&block)
|
pemdata := pem.EncodeToMemory(&block)
|
||||||
|
|
||||||
// Create new TUF Key so we can compute the TUF-compliant CertID
|
keyType := ""
|
||||||
tufKey := data.NewTUFKey("RSA", pemdata, nil)
|
switch cert.PublicKeyAlgorithm {
|
||||||
|
case x509.RSA:
|
||||||
|
keyType = data.RSAx509Key
|
||||||
|
case x509.ECDSA:
|
||||||
|
keyType = data.ECDSAx509Key
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("got Unknown key type while fingerprinting certificate")
|
||||||
|
}
|
||||||
|
|
||||||
return CertID(tufKey.ID())
|
// Create new TUF Key so we can compute the TUF-compliant CertID
|
||||||
|
tufKey := data.NewTUFKey(keyType, pemdata, nil)
|
||||||
|
|
||||||
|
logrus.Debugf("certificate fingerprint generated for key type %s: %s", keyType, tufKey.ID())
|
||||||
|
|
||||||
|
return CertID(tufKey.ID()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadCertsFromDir receives a store AddCertFromFile for each certificate found
|
// loadCertsFromDir receives a store AddCertFromFile for each certificate found
|
||||||
|
@ -187,12 +174,37 @@ func ParsePEMPrivateKey(pemBytes []byte, passphrase string) (*data.PrivateKey, e
|
||||||
return nil, fmt.Errorf("could not parse DER encoded key: %v", err)
|
return nil, fmt.Errorf("could not parse DER encoded key: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tufRSAPrivateKey, err := RSAToPrivateKey(rsaPrivKey)
|
tufRSAPrivateKey, err := RSAToPrivateKey(rsaPrivKey, data.RSAKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not convert rsa.PrivateKey to data.PrivateKey: %v", err)
|
return nil, fmt.Errorf("could not convert rsa.PrivateKey to data.PrivateKey: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return tufRSAPrivateKey, nil
|
return tufRSAPrivateKey, nil
|
||||||
|
case "EC PRIVATE KEY":
|
||||||
|
var privKeyBytes []byte
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if x509.IsEncryptedPEMBlock(block) {
|
||||||
|
privKeyBytes, err = x509.DecryptPEMBlock(block, []byte(passphrase))
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("could not decrypt private key")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
privKeyBytes = block.Bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
ecdsaPrivKey, err := x509.ParseECPrivateKey(privKeyBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not parse DER encoded private key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tufECDSAPrivateKey, err := ECDSAToPrivateKey(ecdsaPrivKey, data.ECDSAKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not convert ecdsa.PrivateKey to data.PrivateKey: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tufECDSAPrivateKey, nil
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unsupported key type %q", block.Type)
|
return nil, fmt.Errorf("unsupported key type %q", block.Type)
|
||||||
}
|
}
|
||||||
|
@ -205,21 +217,111 @@ func GenerateRSAKey(random io.Reader, bits int) (*data.PrivateKey, error) {
|
||||||
return nil, fmt.Errorf("could not generate private key: %v", err)
|
return nil, fmt.Errorf("could not generate private key: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return RSAToPrivateKey(rsaPrivKey)
|
tufPrivKey, err := RSAToPrivateKey(rsaPrivKey, data.RSAKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.Debugf("generated RSA key with keyID: %s", tufPrivKey.ID())
|
||||||
|
|
||||||
|
return tufPrivKey, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RSAToPrivateKey converts an rsa.Private key to a TUF data.PrivateKey type
|
// RSAToPrivateKey converts an rsa.Private key to a TUF data.PrivateKey type
|
||||||
func RSAToPrivateKey(rsaPrivKey *rsa.PrivateKey) (*data.PrivateKey, error) {
|
func RSAToPrivateKey(rsaPrivKey *rsa.PrivateKey, keyType string) (*data.PrivateKey, error) {
|
||||||
// Get a DER-encoded representation of the PublicKey
|
// Get a DER-encoded representation of the PublicKey
|
||||||
rsaPubBytes, err := x509.MarshalPKIXPublicKey(&rsaPrivKey.PublicKey)
|
rsaPubBytes, err := x509.MarshalPKIXPublicKey(&rsaPrivKey.PublicKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to marshal private key: %v", err)
|
return nil, fmt.Errorf("failed to marshal public key: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get a DER-encoded representation of the PrivateKey
|
// Get a DER-encoded representation of the PrivateKey
|
||||||
rsaPrivBytes := x509.MarshalPKCS1PrivateKey(rsaPrivKey)
|
rsaPrivBytes := x509.MarshalPKCS1PrivateKey(rsaPrivKey)
|
||||||
|
|
||||||
return data.NewPrivateKey("RSA", rsaPubBytes, rsaPrivBytes), nil
|
return data.NewPrivateKey(keyType, rsaPubBytes, rsaPrivBytes), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateECDSAKey generates an ECDSA Private key and returns a TUF PrivateKey
|
||||||
|
func GenerateECDSAKey(random io.Reader) (*data.PrivateKey, error) {
|
||||||
|
// TODO(diogo): For now hardcode P256. There were timming attacks on the other
|
||||||
|
// curves, but I can't seem to find the issue.
|
||||||
|
ecdsaPrivKey, err := ecdsa.GenerateKey(elliptic.P256(), random)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tufPrivKey, err := ECDSAToPrivateKey(ecdsaPrivKey, data.ECDSAKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.Debugf("generated ECDSA key with keyID: %s", tufPrivKey.ID())
|
||||||
|
|
||||||
|
return tufPrivKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ECDSAToPrivateKey converts an rsa.Private key to a TUF data.PrivateKey type
|
||||||
|
func ECDSAToPrivateKey(ecdsaPrivKey *ecdsa.PrivateKey, keyType string) (*data.PrivateKey, error) {
|
||||||
|
// Get a DER-encoded representation of the PublicKey
|
||||||
|
ecdsaPubBytes, err := x509.MarshalPKIXPublicKey(&ecdsaPrivKey.PublicKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to marshal public key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a DER-encoded representation of the PrivateKey
|
||||||
|
ecdsaPrivKeyBytes, err := x509.MarshalECPrivateKey(ecdsaPrivKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to marshal private key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return data.NewPrivateKey(keyType, ecdsaPubBytes, ecdsaPrivKeyBytes), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeyToPEM returns a PEM encoded key from a Private Key
|
||||||
|
func KeyToPEM(privKey *data.PrivateKey) ([]byte, error) {
|
||||||
|
var pemType string
|
||||||
|
cipher := privKey.Cipher()
|
||||||
|
|
||||||
|
switch cipher {
|
||||||
|
case data.RSAKey:
|
||||||
|
pemType = "RSA PRIVATE KEY"
|
||||||
|
case data.ECDSAKey:
|
||||||
|
pemType = "EC PRIVATE KEY"
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("only RSA or ECDSA keys are currently supported. Found: %s", cipher)
|
||||||
|
}
|
||||||
|
|
||||||
|
return pem.EncodeToMemory(&pem.Block{Type: pemType, Bytes: privKey.Private()}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncryptPrivateKey returns an encrypted PEM key given a Privatekey
|
||||||
|
// and a passphrase
|
||||||
|
func EncryptPrivateKey(key *data.PrivateKey, passphrase string) ([]byte, error) {
|
||||||
|
var blockType string
|
||||||
|
cipher := key.Cipher()
|
||||||
|
|
||||||
|
switch cipher {
|
||||||
|
case data.RSAKey:
|
||||||
|
blockType = "RSA PRIVATE KEY"
|
||||||
|
case data.ECDSAKey:
|
||||||
|
blockType = "EC PRIVATE KEY"
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("only RSA or ECDSA keys are currently supported. Found: %s", cipher)
|
||||||
|
}
|
||||||
|
|
||||||
|
password := []byte(passphrase)
|
||||||
|
cipherType := x509.PEMCipherAES256
|
||||||
|
|
||||||
|
encryptedPEMBlock, err := x509.EncryptPEMBlock(rand.Reader,
|
||||||
|
blockType,
|
||||||
|
key.Private(),
|
||||||
|
password,
|
||||||
|
cipherType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return pem.EncodeToMemory(encryptedPEMBlock), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCertificate returns an X509 Certificate following a template, given a GUN.
|
// NewCertificate returns an X509 Certificate following a template, given a GUN.
|
||||||
|
|
Loading…
Reference in New Issue