Skip to content
Snippets Groups Projects
Unverified Commit 267085c3 authored by dlorenc's avatar dlorenc Committed by GitHub
Browse files

Merge pull request #146 from dlorenc/ed25519

Add ed25519 support to ssh libraries.
parents 4e1445ff 998428cf
No related branches found
No related tags found
No related merge requests found
......@@ -18,7 +18,7 @@ func Armor(s *ssh.Signature, p ssh.PublicKey) []byte {
Version: 1,
PublicKey: string(p.Marshal()),
Namespace: namespace,
HashAlgorithm: hashAlgorithm,
HashAlgorithm: defaultHashAlgorithm,
Signature: string(ssh.Marshal(s)),
}
copy(sig.MagicHeader[:], []byte(magicHeader))
......@@ -55,7 +55,9 @@ func Decode(b []byte) (*Signature, error) {
if sig.Namespace != "file" {
return nil, fmt.Errorf("invalid signature namespace: %s", sig.Namespace)
}
// TODO: Also check the HashAlgorithm type here.
if _, ok := supportedHashAlgorithms[sig.HashAlgorithm]; !ok {
return nil, fmt.Errorf("unsupported hash algorithm: %s", sig.HashAlgorithm)
}
// Now we can unpack the Signature and PublicKey blocks
sshSig := ssh.Signature{}
......@@ -72,5 +74,6 @@ func Decode(b []byte) (*Signature, error) {
return &Signature{
signature: &sshSig,
pk: pk,
hashAlg: sig.HashAlgorithm,
}, nil
}
......@@ -2,7 +2,9 @@ package ssh
import (
"crypto/rand"
"crypto/sha256"
"crypto/sha512"
"hash"
"io"
"golang.org/x/crypto/ssh"
......@@ -28,12 +30,16 @@ type WrappedSig struct {
}
const (
magicHeader = "SSHSIG"
hashAlgorithm = "sha512"
magicHeader = "SSHSIG"
defaultHashAlgorithm = "sha512"
)
func sign(s ssh.AlgorithmSigner, m io.Reader) (*ssh.Signature, error) {
var supportedHashAlgorithms = map[string]func() hash.Hash{
"sha256": sha256.New,
"sha512": sha512.New,
}
func sign(s ssh.AlgorithmSigner, m io.Reader) (*ssh.Signature, error) {
hf := sha512.New()
if _, err := io.Copy(hf, m); err != nil {
return nil, err
......@@ -42,14 +48,21 @@ func sign(s ssh.AlgorithmSigner, m io.Reader) (*ssh.Signature, error) {
sp := MessageWrapper{
Namespace: "file",
HashAlgorithm: hashAlgorithm,
HashAlgorithm: defaultHashAlgorithm,
Hash: string(mh),
}
dataMessageWrapper := ssh.Marshal(sp)
dataMessageWrapper = append([]byte(magicHeader), dataMessageWrapper...)
sig, err := s.SignWithAlgorithm(rand.Reader, dataMessageWrapper, ssh.SigAlgoRSASHA2512)
// ssh-rsa is not supported for RSA keys:
// https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.sshsig#L71
// We can use the default value of "" for other key types though.
algo := ""
if s.PublicKey().Type() == ssh.KeyAlgoRSA {
algo = ssh.SigAlgoRSASHA2512
}
sig, err := s.SignWithAlgorithm(rand.Reader, dataMessageWrapper, algo)
if err != nil {
return nil, err
}
......
......@@ -94,113 +94,186 @@ Wry/EKQvgvOUjbAAAAFG90aGVyLXRlc3RAcmVrb3IuZGV2AQIDBAUG
`
otherSshPublicKey = `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDD9YJJYL1MS8JA7A75PrwS81rc5Ii/VjT6xPy5K/KM/IQ7T6nS3wKD/PLru87GJClTmM3owi2NcCMyZ4KbwYoy3qCT5wOhoB6/9l+O7NIvtFrITGmaEGV6HZfcYouSRcX0AEU1yGVOpIs5mISMOg2lsW/XopPWwToKpbwOPFdCRCT0krrmEsf4HF5Yw0IQlVoKZrhfThomYLvMkCLnIZ55PRIpWoyiFq8X3Q7peJgUJAe7Bc8/Id+hyqC52ZeejPP7oPprEkpkzBCw2ndYq6Y6OXNafEEIIHWXaM3pFqDxonbbvuIwVdHCNMv/yNoSxbgqTKwN/QaNXb+KpuvSrlvRqsNhu/sKsYFH64fTAbP9miDXHmJkA05uFlQukOstUmJ0QxzbsdcFvs8yw0PLsEZhEHXJzR3TLzenyZSq86VZICvGfVacBk7TikCBOtyWuESHhlc6SfZKfzZ67cOlDyKeSiVjgh+eEh9s4h56ahQ2rW05Sq6GjD0YtEzog2J4csE= other-test@rekor.dev
`
// Generated with ssh-keygen -C test@rekor.dev -t ed25519 -f id_ed25519
ed25519PrivateKey = `-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACBB45zRHxPPFtabwS3Vd6Lb9vMe+tIHZj2qN5VQ+bgLfQAAAJgyRa3cMkWt
3AAAAAtzc2gtZWQyNTUxOQAAACBB45zRHxPPFtabwS3Vd6Lb9vMe+tIHZj2qN5VQ+bgLfQ
AAAED7y4N/DsVnRQiBZNxEWdsJ9RmbranvtQ3X9jnb6gFed0HjnNEfE88W1pvBLdV3otv2
8x760gdmPao3lVD5uAt9AAAADnRlc3RAcmVrb3IuZGV2AQIDBAUGBw==
-----END OPENSSH PRIVATE KEY-----
`
ed25519PublicKey = `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEHjnNEfE88W1pvBLdV3otv28x760gdmPao3lVD5uAt9 test@rekor.dev
`
)
func TestFromOpenSSH(t *testing.T) {
// Test that a signature from the cli can validate here.
td := t.TempDir()
for _, tt := range []struct {
name string
pub string
priv string
}{
{
name: "rsa",
pub: sshPublicKey,
priv: sshPrivateKey,
},
{
name: "ed25519",
pub: ed25519PublicKey,
priv: ed25519PrivateKey,
},
} {
t.Run(tt.name, func(t *testing.T) {
tt := tt
data := []byte("hello, ssh world")
dataPath := write(t, []byte(data), td, "data")
privPath := write(t, []byte(sshPrivateKey), td, "id_rsa")
write(t, []byte(sshPublicKey), td, "id_rsa.pub")
// Test that a signature from the cli can validate here.
td := t.TempDir()
sigPath := dataPath + ".sig"
run(t, nil, "ssh-keygen", "-Y", "sign", "-n", "file", "-f", privPath, dataPath)
data := []byte("hello, ssh world")
dataPath := write(t, []byte(data), td, "data")
sigBytes, err := ioutil.ReadFile(sigPath)
if err != nil {
t.Fatal(err)
}
if err := Verify(bytes.NewReader(data), sigBytes, []byte(sshPublicKey)); err != nil {
t.Error(err)
}
privPath := write(t, []byte(tt.priv), td, "id")
write(t, []byte(tt.pub), td, "id.pub")
// It should not verify if we check against the other public key
if err := Verify(bytes.NewReader(data), sigBytes, []byte(otherSshPublicKey)); err == nil {
t.Error("expected error with incorrect key")
}
sigPath := dataPath + ".sig"
run(t, nil, "ssh-keygen", "-Y", "sign", "-n", "file", "-f", privPath, dataPath)
sigBytes, err := ioutil.ReadFile(sigPath)
if err != nil {
t.Fatal(err)
}
if err := Verify(bytes.NewReader(data), sigBytes, []byte(tt.pub)); err != nil {
t.Error(err)
}
// It should not verify if the data is tampered
if err := Verify(strings.NewReader("bad data"), sigBytes, []byte(sshPublicKey)); err == nil {
t.Error("expected error with incorrect data")
// It should not verify if we check against another public key
if err := Verify(bytes.NewReader(data), sigBytes, []byte(otherSshPublicKey)); err == nil {
t.Error("expected error with incorrect key")
}
// It should not verify if the data is tampered
if err := Verify(strings.NewReader("bad data"), sigBytes, []byte(sshPublicKey)); err == nil {
t.Error("expected error with incorrect data")
}
})
}
}
func TestToOpenSSH(t *testing.T) {
// Test that a signature from here can validate in the CLI.
td := t.TempDir()
for _, tt := range []struct {
name string
pub string
priv string
}{
{
name: "rsa",
pub: sshPublicKey,
priv: sshPrivateKey,
},
{
name: "ed25519",
pub: ed25519PublicKey,
priv: ed25519PrivateKey,
},
} {
t.Run(tt.name, func(t *testing.T) {
tt := tt
// Test that a signature from here can validate in the CLI.
td := t.TempDir()
data := []byte("hello, ssh world")
write(t, []byte(data), td, "data")
write(t, []byte(sshPrivateKey), td, "id_rsa")
data := []byte("hello, ssh world")
write(t, []byte(data), td, "data")
armored, err := Sign(sshPrivateKey, bytes.NewReader(data))
if err != nil {
t.Fatal(err)
}
armored, err := Sign(tt.priv, bytes.NewReader(data))
if err != nil {
t.Fatal(err)
}
sigPath := write(t, []byte(armored), td, "oursig")
sigPath := write(t, []byte(armored), td, "oursig")
// Create an allowed_signers file with two keys to check against.
allowedSigner := "test@rekor.dev " + sshPublicKey + "\n"
allowedSigner += "othertest@rekor.dev " + sshPrivateKey + "\n"
allowedSigners := write(t, []byte(allowedSigner), td, "allowed_signer")
// Create an allowed_signers file with two keys to check against.
allowedSigner := "test@rekor.dev " + tt.pub + "\n"
allowedSigner += "othertest@rekor.dev " + otherSshPublicKey + "\n"
allowedSigners := write(t, []byte(allowedSigner), td, "allowed_signer")
// We use the correct principal here so it should work.
run(t, data, "ssh-keygen", "-Y", "verify", "-f", allowedSigners,
"-I", "test@rekor.dev", "-n", "file", "-s", sigPath)
// We use the correct principal here so it should work.
run(t, data, "ssh-keygen", "-Y", "verify", "-f", allowedSigners,
"-I", "test@rekor.dev", "-n", "file", "-s", sigPath)
// Just to be sure, check against the other public key as well.
runErr(t, data, "ssh-keygen", "-Y", "verify", "-f", allowedSigners,
"-I", "othertest@rekor.dev", "-n", "file", "-s", sigPath)
// Just to be sure, check against the other public key as well.
runErr(t, data, "ssh-keygen", "-Y", "verify", "-f", allowedSigners,
"-I", "othertest@rekor.dev", "-n", "file", "-s", sigPath)
// It should error if we run it against other data
data = []byte("other data!")
runErr(t, data, "ssh-keygen", "-Y", "check-novalidate", "-n", "file", "-s", sigPath)
// It should error if we run it against other data
data = []byte("other data!")
runErr(t, data, "ssh-keygen", "-Y", "check-novalidate", "-n", "file", "-s", sigPath)
})
}
}
func TestRoundTrip(t *testing.T) {
data := []byte("my good data to be signed!")
// Create two signatures from two private keys.
sig, err := Sign(sshPrivateKey, bytes.NewReader(data))
if err != nil {
t.Fatal(err)
}
// Create one extra signature for all the tests.
otherSig, err := Sign(otherSshPrivateKey, bytes.NewReader(data))
if err != nil {
t.Fatal(err)
}
// Check the signature against that data and public key
if err := Verify(bytes.NewReader(data), sig, []byte(sshPublicKey)); err != nil {
t.Error(err)
}
for _, tt := range []struct {
name string
pub string
priv string
}{
{
name: "rsa",
pub: sshPublicKey,
priv: sshPrivateKey,
},
{
name: "ed25519",
pub: ed25519PublicKey,
priv: ed25519PrivateKey,
},
} {
t.Run(tt.name, func(t *testing.T) {
tt := tt
sig, err := Sign(tt.priv, bytes.NewReader(data))
if err != nil {
t.Fatal(err)
}
// Now check it against invalid data.
if err := Verify(strings.NewReader("invalid data!"), sig, []byte(sshPublicKey)); err == nil {
t.Error("expected error!")
}
// Check the signature against that data and public key
if err := Verify(bytes.NewReader(data), sig, []byte(tt.pub)); err != nil {
t.Error(err)
}
// Now check it against the wrong key.
if err := Verify(bytes.NewReader(data), sig, []byte(otherSshPublicKey)); err == nil {
t.Error("expected error!")
}
// Now check it against invalid data.
if err := Verify(strings.NewReader("invalid data!"), sig, []byte(tt.pub)); err == nil {
t.Error("expected error!")
}
// Now check it against an invalid signature data.
if err := Verify(bytes.NewReader(data), []byte("invalid signature!"), []byte(sshPublicKey)); err == nil {
t.Error("expected error!")
}
// Now check it against the wrong key.
if err := Verify(bytes.NewReader(data), sig, []byte(otherSshPublicKey)); err == nil {
t.Error("expected error!")
}
// Once more, use the wrong signature and check it against the original (wrong public key)
if err := Verify(bytes.NewReader(data), otherSig, []byte(sshPublicKey)); err == nil {
t.Error("expected error!")
}
// It should work against the correct public key.
if err := Verify(bytes.NewReader(data), otherSig, []byte(otherSshPublicKey)); err != nil {
t.Error(err)
// Now check it against an invalid signature data.
if err := Verify(bytes.NewReader(data), []byte("invalid signature!"), []byte(tt.pub)); err == nil {
t.Error("expected error!")
}
// Once more, use the wrong signature and check it against the original (wrong public key)
if err := Verify(bytes.NewReader(data), otherSig, []byte(tt.pub)); err == nil {
t.Error("expected error!")
}
// It should work against the correct public key.
if err := Verify(bytes.NewReader(data), otherSig, []byte(otherSshPublicKey)); err != nil {
t.Error(err)
}
})
}
}
......
......@@ -27,6 +27,7 @@ import (
type Signature struct {
signature *ssh.Signature
pk ssh.PublicKey
hashAlg string
}
// NewSignature creates and Validates an ssh signature object
......
package ssh
import (
"crypto/sha512"
"io"
"golang.org/x/crypto/ssh"
......@@ -19,7 +18,7 @@ func Verify(message io.Reader, armoredSignature []byte, publicKey []byte) error
}
// Hash the message so we can verify it against the signature.
h := sha512.New()
h := supportedHashAlgorithms[decodedSignature.hashAlg]()
if _, err := io.Copy(h, message); err != nil {
return err
}
......@@ -27,7 +26,7 @@ func Verify(message io.Reader, armoredSignature []byte, publicKey []byte) error
toVerify := MessageWrapper{
Namespace: "file",
HashAlgorithm: "sha512",
HashAlgorithm: decodedSignature.hashAlg,
Hash: string(hm),
}
signedMessage := ssh.Marshal(toVerify)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment