diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 4119fb5871..4798a7a648 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -37,6 +37,11 @@ "Comment": "v2.0.0-rc.3-2-g75919b7", "Rev": "75919b7dcc5d53894d7e8f1584e91ae148335f3a" }, + { + "ImportPath": "github.com/docker/docker/pkg/term", + "Comment": "v1.4.1-775-g70fbd45", + "Rev": "70fbd45a5c88f6f39a07b04f81a07721bf5f3eed" + }, { "ImportPath": "github.com/docker/libtrust", "Rev": "fa567046d9b14f6aa788882a950d69651d230b21" @@ -47,7 +52,7 @@ }, { "ImportPath": "github.com/endophage/gotuf", - "Rev": "af7152a51a0663dc47768e591ffdfa1f60e5308a" + "Rev": "ab4ba80203ffa5bfd742e6891bd28bfbf43a9453" }, { "ImportPath": "github.com/go-sql-driver/mysql", diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/term/MAINTAINERS b/Godeps/_workspace/src/github.com/docker/docker/pkg/term/MAINTAINERS new file mode 100644 index 0000000000..aee10c8421 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/term/MAINTAINERS @@ -0,0 +1 @@ +Solomon Hykes (@shykes) diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/term/console_windows.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/term/console_windows.go new file mode 100644 index 0000000000..6335b2b837 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/term/console_windows.go @@ -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 +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/term/tc_linux_cgo.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/term/tc_linux_cgo.go new file mode 100644 index 0000000000..ae9516c99c --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/term/tc_linux_cgo.go @@ -0,0 +1,47 @@ +// +build linux,cgo + +package term + +import ( + "syscall" + "unsafe" +) + +// #include +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 +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/term/tc_other.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/term/tc_other.go new file mode 100644 index 0000000000..266039bac3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/term/tc_other.go @@ -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 +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/term/term.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/term/term.go new file mode 100644 index 0000000000..8d807d8d44 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/term/term.go @@ -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) + }() +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/term/term_windows.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/term/term_windows.go new file mode 100644 index 0000000000..d372e86a88 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/term/term_windows.go @@ -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 +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/term/termios_darwin.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/term/termios_darwin.go new file mode 100644 index 0000000000..11cd70d10b --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/term/termios_darwin.go @@ -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 +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/term/termios_freebsd.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/term/termios_freebsd.go new file mode 100644 index 0000000000..ed3659572c --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/term/termios_freebsd.go @@ -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 +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/term/termios_linux.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/term/termios_linux.go new file mode 100644 index 0000000000..024187ff06 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/term/termios_linux.go @@ -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 +} diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/data/keys.go b/Godeps/_workspace/src/github.com/endophage/gotuf/data/keys.go index aeb837fef7..4287ef8ec4 100644 --- a/Godeps/_workspace/src/github.com/endophage/gotuf/data/keys.go +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/data/keys.go @@ -41,9 +41,7 @@ func (k TUFKey) Cipher() string { } func (k *TUFKey) ID() string { - logrus.Debug("Generating Key ID") if k.id == "" { - logrus.Debug("Generating Key ID") pubK := NewTUFKey(k.Cipher(), k.Public(), nil) data, err := cjson.Marshal(&pubK) if err != nil { diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/data/snapshot.go b/Godeps/_workspace/src/github.com/endophage/gotuf/data/snapshot.go index b3acdd7713..06290cd972 100644 --- a/Godeps/_workspace/src/github.com/endophage/gotuf/data/snapshot.go +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/data/snapshot.go @@ -23,7 +23,7 @@ type Snapshot struct { } func NewSnapshot(root *Signed, targets *Signed) (*SignedSnapshot, error) { - logrus.Debug("NewSnapshot") + logrus.Debug("generating new snapshot...") targetsJSON, err := json.Marshal(targets) if err != nil { logrus.Debug("Error Marshalling Targets") diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/data/types.go b/Godeps/_workspace/src/github.com/endophage/gotuf/data/types.go index f35e11cca6..eccaf2e3ef 100644 --- a/Godeps/_workspace/src/github.com/endophage/gotuf/data/types.go +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/data/types.go @@ -13,7 +13,17 @@ import ( "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{ "targets": "Targets", diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/signed/sign.go b/Godeps/_workspace/src/github.com/endophage/gotuf/signed/sign.go index c0bfa5450c..3095e2a78f 100644 --- a/Godeps/_workspace/src/github.com/endophage/gotuf/signed/sign.go +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/signed/sign.go @@ -18,29 +18,27 @@ func NewSigner(service CryptoService) *Signer { // Sign takes a data.Signed and a key, calculated and adds the signature // to the data.Signed 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) keyIDMemb := make(map[string]struct{}) keyIDs := make([]string, 0, len(keys)) - logrus.Debug("Generate list of signing IDs") + for _, key := range keys { keyIDMemb[key.ID()] = struct{}{} 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 { if _, ok := keyIDMemb[sig.KeyID]; ok { continue } signatures = append(signatures, sig) } - logrus.Debug("Performing Signing") newSigs, err := signer.service.Sign(keyIDs, s.Signed) if err != nil { return err } - - logrus.Debug("Updating signatures slice") + logrus.Debugf("appending %d new signatures", len(newSigs)) s.Signatures = append(signatures, newSigs...) return nil } diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/signed/sign_test.go b/Godeps/_workspace/src/github.com/endophage/gotuf/signed/sign_test.go index 64a799b40e..7a565f7c3a 100644 --- a/Godeps/_workspace/src/github.com/endophage/gotuf/signed/sign_test.go +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/signed/sign_test.go @@ -41,7 +41,7 @@ var _ CryptoService = &MockCryptoService{} // Test signing and ensure the expected signature is added func TestBasicSign(t *testing.T) { testKey, _ := pem.Decode([]byte(testKeyPEM1)) - k := data.NewPublicKey("RSA", testKey.Bytes) + k := data.NewPublicKey(data.RSAKey, testKey.Bytes) signer := Signer{&MockCryptoService{ testKey: *k, }} @@ -68,7 +68,7 @@ func TestBasicSign(t *testing.T) { // should be cleaning previous signatures by the KeyID when asked to sign again) func TestReSign(t *testing.T) { testKey, _ := pem.Decode([]byte(testKeyPEM1)) - k := data.NewPublicKey("RSA", testKey.Bytes) + k := data.NewPublicKey(data.RSAKey, testKey.Bytes) signer := Signer{&MockCryptoService{ testKey: *k, }} @@ -92,11 +92,11 @@ func TestMultiSign(t *testing.T) { testData := data.Signed{} testKey, _ := pem.Decode([]byte(testKeyPEM1)) - key := data.NewPublicKey("RSA", testKey.Bytes) + key := data.NewPublicKey(data.RSAKey, testKey.Bytes) signer.Sign(&testData, key) testKey, _ = pem.Decode([]byte(testKeyPEM2)) - key = data.NewPublicKey("RSA", testKey.Bytes) + key = data.NewPublicKey(data.RSAKey, testKey.Bytes) signer.Sign(&testData, key) if len(testData.Signatures) != 2 { @@ -114,7 +114,7 @@ func TestMultiSign(t *testing.T) { func TestCreate(t *testing.T) { testKey, _ := pem.Decode([]byte(testKeyPEM1)) - k := data.NewPublicKey("RSA", testKey.Bytes) + k := data.NewPublicKey(data.RSAKey, testKey.Bytes) signer := Signer{&MockCryptoService{ testKey: *k, }} diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/signed/verifiers.go b/Godeps/_workspace/src/github.com/endophage/gotuf/signed/verifiers.go index 90f66a491c..2a3c0fb3ac 100644 --- a/Godeps/_workspace/src/github.com/endophage/gotuf/signed/verifiers.go +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/signed/verifiers.go @@ -2,10 +2,12 @@ package signed import ( "crypto" + "crypto/ecdsa" "crypto/rsa" "crypto/sha256" "crypto/x509" "encoding/pem" + "math/big" "reflect" "github.com/Sirupsen/logrus" @@ -17,10 +19,10 @@ import ( // can be injected into a verificationService. For testing and configuration // purposes, it will not be used by default. var Verifiers = map[string]Verifier{ - "ed25519": Ed25519Verifier{}, - "rsassa-pss": RSAPSSVerifier{}, - "rsassa-pss-x509": RSAPSSX509Verifier{}, - "pycrypto-pkcs#1 pss": RSAPyCryptoVerifier{}, + data.RSAPSSSignature: RSAPSSVerifier{}, + data.PyCryptoSignature: RSAPyCryptoVerifier{}, + data.ECDSASignature: ECDSAVerifier{}, + data.EDDSASignature: Ed25519Verifier{}, } // RegisterVerifier provides a convenience function for init() functions @@ -31,12 +33,12 @@ func RegisterVerifier(name string, v Verifier) { typOld := reflect.TypeOf(curr) typNew := reflect.TypeOf(v) logrus.Debugf( - "Replacing already loaded verifier %s:%s with %s:%s", + "replacing already loaded verifier %s:%s with %s:%s", typOld.PkgPath(), typOld.Name(), typNew.PkgPath(), typNew.Name(), ) } else { - logrus.Debug("Adding verifier for: ", name) + logrus.Debug("adding verifier for: ", name) } Verifiers[name] = v } @@ -46,7 +48,7 @@ type Ed25519Verifier struct{} func (v Ed25519Verifier) Verify(key data.Key, sig []byte, msg []byte) error { var sigBytes [ed25519.SignatureSize]byte 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 } copy(sigBytes[:], sig) @@ -55,7 +57,7 @@ func (v Ed25519Verifier) Verify(key data.Key, sig []byte, msg []byte) error { copy(keyBytes[:], key.Public()) if !ed25519.Verify(&keyBytes, msg, &sigBytes) { - logrus.Infof("Failed ed25519 verification") + logrus.Infof("failed ed25519 verification") return ErrInvalid } 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 { rsaPub, ok := key.(*rsa.PublicKey) if !ok { - logrus.Infof("Value was not an RSA public key") + logrus.Infof("value was not an RSA public key") return ErrInvalid } opts := rsa.PSSOptions{SaltLength: sha256.Size, Hash: crypto.SHA256} 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 nil @@ -81,15 +83,37 @@ type RSAPSSVerifier struct{} // Verify does the actual check. 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()) - if err != nil { - logrus.Infof("Failed to parse public key: %s\n", err) + switch cipher { + case data.RSAx509Key: + 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 verifyPSS(pub, digest[:], sig) + digest := sha256.Sum256(msg) + + return verifyPSS(pubKey, digest[:], sig) } // 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())) if k == nil { - logrus.Infof("Failed to decode PEM-encoded x509 certificate") + logrus.Infof("failed to decode PEM-encoded x509 certificate") return ErrInvalid } pub, err := x509.ParsePKIXPublicKey(k.Bytes) 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 verifyPSS(pub, digest[:], sig) } -// RSAPSSPEMVerifier checks RSASSA-PSS signatures, extracting the public key -// from an X509 certificate. -type RSAPSSX509Verifier struct{} +// ECDSAVerifier checks ECDSA signatures, decoding the keyType appropriately +type ECDSAVerifier struct{} // 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) - k, _ := pem.Decode([]byte(key.Public())) - if k == nil { - 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) + if !ecdsa.Verify(ecdsaPubKey, digest[:], r, s) { + logrus.Infof("failed ECDSA signature validation") return ErrInvalid } - return verifyPSS(cert.PublicKey, digest[:], sig) + return nil } diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/signed/verifiers_test.go b/Godeps/_workspace/src/github.com/endophage/gotuf/signed/verifiers_test.go index 4f57d2ea6b..279442511e 100644 --- a/Godeps/_workspace/src/github.com/endophage/gotuf/signed/verifiers_test.go +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/signed/verifiers_test.go @@ -1,13 +1,298 @@ package signed import ( - _ "crypto" - _ "crypto/rsa" - _ "crypto/sha256" - _ "crypto/x509" + "bytes" + "crypto" + "crypto/ecdsa" + "crypto/rand" + "crypto/rsa" + "crypto/sha256" + "crypto/x509" + "encoding/hex" + "encoding/json" + "fmt" "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 +} diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/signed/verify.go b/Godeps/_workspace/src/github.com/endophage/gotuf/signed/verify.go index bdc3bb89d5..1767e3e62f 100644 --- a/Godeps/_workspace/src/github.com/endophage/gotuf/signed/verify.go +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/signed/verify.go @@ -49,7 +49,7 @@ func VerifyRoot(s *data.Signed, minVersion int, keys map[string]*data.PublicKey, method := strings.ToLower(sig.Method) verifier, ok := Verifiers[method] 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 } @@ -78,11 +78,6 @@ func verifyMeta(s *data.Signed, role string, minVersion int) error { if err := json.Unmarshal(s.Signed, sm); err != nil { 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) { return ErrWrongType } diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/tuf.go b/Godeps/_workspace/src/github.com/endophage/gotuf/tuf.go index ce5c7f036f..eb9410e830 100644 --- a/Godeps/_workspace/src/github.com/endophage/gotuf/tuf.go +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/tuf.go @@ -260,9 +260,8 @@ func (tr *TufRepo) SetRoot(s *data.Signed) error { if err != nil { return err } - for kid, key := range r.Signed.Keys { + for _, key := range r.Signed.Keys { tr.keysDB.AddKey(key) - logrus.Debug("Given Key ID:", kid, "\nGenerated Key ID:", key.ID()) } for roleName, role := range r.Signed.Roles { 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) { - logrus.Debug("SignRoot") + logrus.Debug("signing root...") if tr.Root.Dirty { 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) { - logrus.Debug("SignTargets") - logrus.Debug("Got targets data.Signed object") + logrus.Debugf("sign targets called for role %s", role) if tr.Targets[role].Dirty { tr.Targets[role].Signed.Version++ 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 } targets := tr.keysDB.GetRole(role) - logrus.Debug("About to sign ", role) signed, err = tr.sign(signed, *targets, signer) if err != nil { logrus.Debug("errored signing ", role) return nil, err } - logrus.Debug("success signing ", role) tr.Targets[role].Signatures = signed.Signatures return signed, nil } 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) { - logrus.Debug("SignSnapshot") + logrus.Debug("signing snapshot...") if tr.Root.Dirty { signedRoot, err := tr.SignRoot(data.DefaultExpires("root"), signer) if err != nil { diff --git a/client/cli_crypto_service.go b/client/cli_crypto_service.go index 41342375fc..3b9aa8a3f5 100644 --- a/client/cli_crypto_service.go +++ b/client/cli_crypto_service.go @@ -2,92 +2,111 @@ package client import ( "crypto" + "crypto/ecdsa" "crypto/rand" "crypto/rsa" "crypto/sha256" "crypto/x509" "fmt" "path/filepath" - "strings" + "github.com/Sirupsen/logrus" "github.com/docker/notary/trustmanager" "github.com/endophage/gotuf/data" ) -// CryptoService implements Sign and Create, holding a specific GUN and keystore to -// operate on -type CryptoService struct { +type genericCryptoService struct { gun string passphrase string keyStore *trustmanager.KeyFileStore } -// NewCryptoService returns an instance of CryptoService -func NewCryptoService(gun string, keyStore *trustmanager.KeyFileStore) *CryptoService { - return &CryptoService{gun: gun, keyStore: keyStore} +// RSACryptoService implements Sign and Create, holding a specific GUN and keystore to +// operate on +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 -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) if err != nil { 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 - 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 } -// SetPassphrase tells the cryptoservice the passphrase. Use only if the key needs -// to be decrypted. -func (ccs *CryptoService) SetPassphrase(passphrase string) { - ccs.passphrase = passphrase -} - -// 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) { +// 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 *RSACryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signature, error) { // Create hasher and hash data hash := crypto.SHA256 hashed := sha256.Sum256(payload) 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 - keyName := filepath.Join(ccs.gun, fingerprint) + keyName := filepath.Join(ccs.gun, keyid) var privKey *data.PrivateKey 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 != "" { // This is a root key privKey, err = ccs.keyStore.GetDecryptedKey(keyName, ccs.passphrase) - method = "RSASSA-PSS-X509" } else { privKey, err = ccs.keyStore.GetKey(keyName) - method = "RSASSA-PSS" } 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. + logrus.Debugf("ignoring error attempting to retrieve RSA key ID: %s, %v", keyid, err) continue } - sig, err := sign(privKey, hash, hashed[:]) + sig, err := rsaSign(privKey, hash, hashed[:]) 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 signatures = append(signatures, data.Signature{ - KeyID: fingerprint, - Method: method, + KeyID: keyid, + Method: data.RSAPSSSignature, Signature: sig[:], }) } @@ -95,9 +114,8 @@ func (ccs *CryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signatur return signatures, nil } -func sign(privKey *data.PrivateKey, hash crypto.Hash, hashed []byte) ([]byte, error) { - // TODO(diogo): Implement support for ECDSA. - if strings.ToLower(privKey.Cipher()) != "rsa" { +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()) } @@ -107,7 +125,7 @@ func sign(privKey *data.PrivateKey, hash crypto.Hash, hashed []byte) ([]byte, er 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}) if err != nil { return nil, err @@ -115,3 +133,118 @@ func sign(privKey *data.PrivateKey, hash crypto.Hash, hashed []byte) ([]byte, er 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 +} diff --git a/client/client.go b/client/client.go index b3122a26c2..9cea571efb 100644 --- a/client/client.go +++ b/client/client.go @@ -2,7 +2,10 @@ package client import ( "bytes" + "crypto" + "crypto/ecdsa" "crypto/rand" + "crypto/rsa" "crypto/x509" "encoding/json" "encoding/pem" @@ -12,6 +15,7 @@ import ( "net/http" "os" "path/filepath" + "strings" "time" "github.com/Sirupsen/logrus" @@ -111,8 +115,8 @@ func NewNotaryRepository(baseDir, gun, baseURL string, rt http.RoundTripper) (*N return nil, err } - logrus.Debugf("creating non-root cryptoservice") - signer := signed.NewSigner(NewCryptoService(gun, privKeyStore)) + // TODO(diogo): This hardcodes snapshots and targets to using EC. Change it. + signer := signed.NewSigner(NewECDSACryptoService(gun, privKeyStore, "")) nRepo := &NotaryRepository{ gun: gun, @@ -139,12 +143,36 @@ func (r *NotaryRepository) Initialize(uSigner *UnlockedSigner) error { return err } 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()) if err != nil { return err } + // All the timestamp keys are generated by the remote server. remote, err := getRemoteStore(r.baseURL, r.gun, r.roundTrip) rawTSKey, err := remote.GetKey("timestamp") if err != nil { @@ -157,8 +185,11 @@ func (r *NotaryRepository) Initialize(uSigner *UnlockedSigner) error { return err } + // Turn the JSON timestamp key from the remote server into a TUFKey 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") if err != nil { return err @@ -534,29 +565,32 @@ func (r *NotaryRepository) validateRoot(root *data.Signed) error { } 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 // public key entry. Need to fix when we want to pass in chains. - k, _ := pem.Decode([]byte(rootSigned.Keys[fingerprint].Public())) - logrus.Debug("Root PEM: ", k) - logrus.Debug("Root ID: ", fingerprint) + k, _ := pem.Decode([]byte(rootSigned.Keys[keyID].Public())) decodedCerts, err := x509.ParseCertificates(k.Bytes) if err != nil { + logrus.Debugf("error while parsing root certificate with keyID: %s, %v", keyID, err) continue } // 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). 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. // 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 // ID gets computed. - _, err = r.certificateStore.GetCertificateByFingerprint(leafID) + _, err = r.certificateStore.GetCertificateByKeyID(leafID) 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 @@ -564,7 +598,7 @@ func (r *NotaryRepository) validateRoot(root *data.Signed) error { certList := []*x509.Certificate{leafCert} err = trustmanager.Verify(r.caStore, r.gun, certList) 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 -func (r *NotaryRepository) GenRootKey(passphrase string) (string, error) { - privKey, err := trustmanager.GenerateRSAKey(rand.Reader, rsaRootKeySize) +func (r *NotaryRepository) GenRootKey(algorithm, passphrase string) (string, error) { + 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 { - 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) 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) { privKey, err := r.rootKeyStore.GetDecryptedKey(rootKeyID, passphrase) 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 - // when a root key is needed. - + var signer *signed.Signer + cipher := privKey.Cipher() // Passing an empty GUN because root keys aren't associated with a GUN. - ccs := NewCryptoService("", r.rootKeyStore) - ccs.SetPassphrase(passphrase) - signer := signed.NewSigner(ccs) + switch strings.ToLower(cipher) { + case data.RSAKey: + 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{ privKey: privKey, @@ -700,9 +750,26 @@ func (uk *UnlockedSigner) PublicKey() *data.PublicKey { // GenerateCertificate generates an X509 Certificate from a template, given a GUN 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 { - 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) @@ -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) } - derBytes, err := x509.CreateCertificate(rand.Reader, template, template, privKey.Public(), privKey) + derBytes, err := x509.CreateCertificate(rand.Reader, template, template, publicKey, privateKey) if err != nil { return nil, fmt.Errorf("failed to create the certificate for: %s (%v)", gun, err) } diff --git a/client/client_test.go b/client/client_test.go index 7183534b93..084c560e72 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -17,7 +17,12 @@ import ( "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) { 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) { // Also contains the private key, but for the purpose of this // test, we don't care - fmt.Fprint(w, timestampKeyJSON) + fmt.Fprint(w, timestampECDSAKeyJSON) }) 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 // sure the repository looks correct on disk. +// We test this with both an RSA and ECDSA root key func TestInitRepo(t *testing.T) { + testInitRepo(t, data.RSAKey) + testInitRepo(t, data.ECDSAKey) +} + +func testInitRepo(t *testing.T, rootType string) { gun := "docker.com/notary" // Temporary directory where test files will be created tempBaseDir, err := ioutil.TempDir("", "notary-test-") @@ -50,11 +61,11 @@ func TestInitRepo(t *testing.T) { repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport) 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) 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) assert.NoError(t, err, "error creating repository: %s", err) @@ -96,7 +107,8 @@ func TestInitRepo(t *testing.T) { certificates := repo.certificateStore.GetCertificates() 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")) 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. // Using ListTargets involves serving signed metadata files over the test's // internal HTTP server. +// We test this with both an RSA and ECDSA root key 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 tempBaseDir, err := ioutil.TempDir("", "notary-test-") defer os.RemoveAll(tempBaseDir) @@ -185,7 +203,7 @@ func TestAddListTarget(t *testing.T) { repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport) 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) rootSigner, err := repo.GetRootSigner(rootKeyID, "passphrase") @@ -285,7 +303,7 @@ func TestAddListTarget(t *testing.T) { assert.NoError(t, err, "could not apply changelist") var tempKey data.PrivateKey - json.Unmarshal([]byte(timestampKeyJSON), &tempKey) + json.Unmarshal([]byte(timestampECDSAKeyJSON), &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 // key is a valid x509 certificate. 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 tempBaseDir, err := ioutil.TempDir("", "notary-test-") defer os.RemoveAll(tempBaseDir) @@ -363,7 +386,7 @@ func TestValidateRootKey(t *testing.T) { repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport) 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) rootSigner, err := repo.GetRootSigner(rootKeyID, "passphrase") diff --git a/cmd/notary/keys.go b/cmd/notary/keys.go index 7b3cc58923..cc7831e03f 100644 --- a/cmd/notary/keys.go +++ b/cmd/notary/keys.go @@ -64,7 +64,7 @@ func keysRemove(cmd *cobra.Command, args []string) { gunOrID := args[0] // Try to retrieve the ID from the CA store. - cert, err := caStore.GetCertificateByFingerprint(gunOrID) + cert, err := caStore.GetCertificateByKeyID(gunOrID) if err == nil { fmt.Printf("Removing: ") printCert(cert) @@ -78,7 +78,7 @@ func keysRemove(cmd *cobra.Command, args []string) { } // Try to retrieve the ID from the Certificate store. - cert, err = certificateStore.GetCertificateByFingerprint(gunOrID) + cert, err = certificateStore.GetCertificateByKeyID(gunOrID) if err == nil { fmt.Printf("Removing: ") printCert(cert) @@ -216,17 +216,21 @@ func keysGenerate(cmd *cobra.Command, args []string) { func printCert(cert *x509.Certificate) { timeDifference := cert.NotAfter.Sub(time.Now()) - fingerprint := trustmanager.FingerprintCert(cert) - fmt.Printf("%s %s (expires in: %v days)\n", cert.Subject.CommonName, fingerprint, math.Floor(timeDifference.Hours()/24)) + keyID, err := trustmanager.FingerprintCert(cert) + 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) { keyPath = strings.TrimSuffix(keyPath, filepath.Ext(keyPath)) keyPath = strings.TrimPrefix(keyPath, viper.GetString("privDir")) - fingerprint := filepath.Base(keyPath) + keyID := filepath.Base(keyPath) gun := filepath.Dir(keyPath)[1:] - fmt.Printf("%s %s\n", gun, fingerprint) + fmt.Printf("%s %s\n", gun, keyID) } func askConfirm() bool { diff --git a/cmd/notary/tuf.go b/cmd/notary/tuf.go index fb64eb5a5e..2d518a5a1a 100644 --- a/cmd/notary/tuf.go +++ b/cmd/notary/tuf.go @@ -1,6 +1,8 @@ package main import ( + "bufio" + "bytes" "crypto/sha256" "errors" "fmt" @@ -9,7 +11,9 @@ import ( "os" "github.com/Sirupsen/logrus" + "github.com/docker/docker/pkg/term" notaryclient "github.com/docker/notary/client" + "github.com/endophage/gotuf/data" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -116,7 +120,7 @@ func tufInit(cmd *cobra.Command, args []string) { if err != nil { fatalf(err.Error()) } - rootKeyID, err = nRepo.GenRootKey(passphrase) + rootKeyID, err = nRepo.GenRootKey(data.ECDSAKey, passphrase) if err != nil { fatalf(err.Error()) } @@ -279,3 +283,43 @@ func passphraseRetriever() (string, error) { } 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 +} diff --git a/server/storage/memory_test.go b/server/storage/memory_test.go index 0040b5b04d..c6a7f00d53 100644 --- a/server/storage/memory_test.go +++ b/server/storage/memory_test.go @@ -3,6 +3,7 @@ package storage import ( "testing" + "github.com/endophage/gotuf/data" "github.com/stretchr/testify/assert" ) @@ -46,23 +47,23 @@ func TestGetTimestampKey(t *testing.T) { //_, _, err := s.GetTimestampKey("gun") //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") 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") } func TestSetTimestampKey(t *testing.T) { 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") 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") } diff --git a/trustmanager/x509filestore.go b/trustmanager/x509filestore.go index 82c6c1b5cc..e4cb5fb018 100644 --- a/trustmanager/x509filestore.go +++ b/trustmanager/x509filestore.go @@ -70,17 +70,19 @@ func (s X509FileStore) AddCert(cert *x509.Certificate) error { // addNamedCert allows adding a certificate while controling the filename it gets // stored under. If the file does not exist on disk, saves it. func (s X509FileStore) addNamedCert(cert *x509.Certificate) error { - fingerprint := fingerprintCert(cert) - logrus.Debug("Adding cert with fingerprint: ", fingerprint) + fileName, keyID, err := fileName(cert) + if err != nil { + return err + } + + logrus.Debug("Adding cert with keyID: ", keyID) // 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") } // Convert certificate to PEM certBytes := CertToPEM(cert) - // Compute FileName - fileName := fileName(cert) // Save the file to disk if not already there. 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 - s.fingerprintMap[fingerprint] = cert - s.fileMap[fingerprint] = fileName + s.fingerprintMap[keyID] = cert + s.fileMap[keyID] = fileName name := string(cert.RawSubject) - s.nameMap[name] = append(s.nameMap[name], fingerprint) + s.nameMap[name] = append(s.nameMap[name], keyID) return nil } @@ -107,10 +109,13 @@ func (s X509FileStore) RemoveCert(cert *x509.Certificate) error { return errors.New("removing nil Certificate from X509Store") } - fingerprint := fingerprintCert(cert) - delete(s.fingerprintMap, fingerprint) - filename := s.fileMap[fingerprint] - delete(s.fileMap, fingerprint) + keyID, err := fingerprintCert(cert) + if err != nil { + return err + } + delete(s.fingerprintMap, keyID) + filename := s.fileMap[keyID] + delete(s.fileMap, keyID) name := string(cert.RawSubject) @@ -118,7 +123,7 @@ func (s X509FileStore) RemoveCert(cert *x509.Certificate) error { fpList := s.nameMap[name] newfpList := fpList[:0] for _, x := range fpList { - if x != fingerprint { + if x != keyID { newfpList = append(newfpList, x) } } @@ -174,15 +179,15 @@ func (s X509FileStore) GetCertificatePool() *x509.CertPool { return pool } -// GetCertificateByFingerprint returns the certificate that matches a certain kID or error -func (s X509FileStore) GetCertificateByFingerprint(hexkID string) (*x509.Certificate, error) { +// GetCertificateByKeyID returns the certificate that matches a certain keyID or error +func (s X509FileStore) GetCertificateByKeyID(keyID string) (*x509.Certificate, 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") } // 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 } @@ -207,6 +212,11 @@ func (s X509FileStore) GetVerifyOptions(dnsName string) (x509.VerifyOptions, err return opts, nil } -func fileName(cert *x509.Certificate) string { - return path.Join(cert.Subject.CommonName, FingerprintCert(cert)) +func fileName(cert *x509.Certificate) (string, CertID, error) { + keyID, err := fingerprintCert(cert) + if err != nil { + return "", "", err + } + + return path.Join(cert.Subject.CommonName, string(keyID)), keyID, nil } diff --git a/trustmanager/x509memstore.go b/trustmanager/x509memstore.go index 0cb8742507..010af66879 100644 --- a/trustmanager/x509memstore.go +++ b/trustmanager/x509memstore.go @@ -47,11 +47,14 @@ func (s X509MemStore) AddCert(cert *x509.Certificate) error { 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) - s.nameMap[name] = append(s.nameMap[name], fingerprint) + s.nameMap[name] = append(s.nameMap[name], keyID) return nil } @@ -62,15 +65,18 @@ func (s X509MemStore) RemoveCert(cert *x509.Certificate) error { return errors.New("removing nil Certificate to X509Store") } - fingerprint := fingerprintCert(cert) - delete(s.fingerprintMap, fingerprint) + keyID, err := fingerprintCert(cert) + if err != nil { + return err + } + delete(s.fingerprintMap, keyID) name := string(cert.RawSubject) // Filter the fingerprint out of this name entry fpList := s.nameMap[name] newfpList := fpList[:0] for _, x := range fpList { - if x != fingerprint { + if x != keyID { newfpList = append(newfpList, x) } } @@ -139,15 +145,15 @@ func (s X509MemStore) GetCertificatePool() *x509.CertPool { return pool } -// GetCertificateByFingerprint returns the certificate that matches a certain kID or error -func (s X509MemStore) GetCertificateByFingerprint(hexkID string) (*x509.Certificate, error) { +// GetCertificateByKeyID returns the certificate that matches a certain keyID or error +func (s X509MemStore) GetCertificateByKeyID(keyID string) (*x509.Certificate, 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") } // 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 } diff --git a/trustmanager/x509memstore_test.go b/trustmanager/x509memstore_test.go index d3449593c8..d9f257e380 100644 --- a/trustmanager/x509memstore_test.go +++ b/trustmanager/x509memstore_test.go @@ -106,20 +106,20 @@ func TestRemoveCert(t *testing.T) { } } -func TestInexistentGetCertificateByFingerprint(t *testing.T) { +func TestInexistentGetCertificateByKeyID(t *testing.T) { store := NewX509MemStore() err := store.AddCertFromFile("../fixtures/notary/root-ca.crt") if err != nil { t.Fatalf("failed to load certificate from file: %v", err) } - _, err = store.GetCertificateByFingerprint("4d06afd30b8bed131d2a84c97d00b37f422021598bfae34285ce98e77b708b5a") + _, err = store.GetCertificateByKeyID("4d06afd30b8bed131d2a84c97d00b37f422021598bfae34285ce98e77b708b5a") if err == nil { 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") if err != nil { 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) } - 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 - _, err = store.GetCertificateByFingerprint(certFingerprint) + _, err = store.GetCertificateByKeyID(keyID) if err != nil { - t.Fatalf("expected certificate in store: %s", certFingerprint) + t.Fatalf("expected certificate in store: %s", keyID) } } diff --git a/trustmanager/x509store.go b/trustmanager/x509store.go index 13c6633173..85307b5fce 100644 --- a/trustmanager/x509store.go +++ b/trustmanager/x509store.go @@ -14,7 +14,7 @@ type X509Store interface { AddCertFromPEM(pemCerts []byte) error AddCertFromFile(filename string) error RemoveCert(cert *x509.Certificate) error - GetCertificateByFingerprint(fingerprint string) (*x509.Certificate, error) + GetCertificateByKeyID(keyID string) (*x509.Certificate, error) GetCertificates() []*x509.Certificate GetCertificatePool() *x509.CertPool GetVerifyOptions(dnsName string) (x509.VerifyOptions, error) diff --git a/trustmanager/x509utils.go b/trustmanager/x509utils.go index 3bfd817d63..614ad8b87a 100644 --- a/trustmanager/x509utils.go +++ b/trustmanager/x509utils.go @@ -1,6 +1,8 @@ package trustmanager import ( + "crypto/ecdsa" + "crypto/elliptic" "crypto/rand" "crypto/rsa" "crypto/x509" @@ -15,6 +17,7 @@ import ( "net/url" "time" + "github.com/Sirupsen/logrus" "github.com/endophage/gotuf/data" ) @@ -59,39 +62,6 @@ func CertToPEM(cert *x509.Certificate) []byte { 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 // if nothing is found. Taken from https://golang.org/src/crypto/x509/cert_pool.go#L85. 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 -func FingerprintCert(cert *x509.Certificate) string { - return string(fingerprintCert(cert)) +func FingerprintCert(cert *x509.Certificate) (string, error) { + 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} pemdata := pem.EncodeToMemory(&block) - // Create new TUF Key so we can compute the TUF-compliant CertID - tufKey := data.NewTUFKey("RSA", pemdata, nil) + keyType := "" + 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 @@ -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) } - tufRSAPrivateKey, err := RSAToPrivateKey(rsaPrivKey) + tufRSAPrivateKey, err := RSAToPrivateKey(rsaPrivKey, data.RSAKey) if err != nil { return nil, fmt.Errorf("could not convert rsa.PrivateKey to data.PrivateKey: %v", err) } 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: 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 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 -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 rsaPubBytes, err := x509.MarshalPKIXPublicKey(&rsaPrivKey.PublicKey) 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 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.