openssh.go 2.05 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Code originally from https://go-review.googlesource.com/c/crypto/+/218620

// TODO: replace with upstream once the above change is merged and released.

package format

import (
	"crypto"
	"crypto/ed25519"
	"crypto/rand"
	"encoding/binary"
	"encoding/pem"
	"fmt"

	"golang.org/x/crypto/ssh"
)

const privateKeyAuthMagic = "openssh-key-v1\x00"

type openSSHEncryptedPrivateKey struct {
	CipherName string
	KDFName    string
	KDFOptions string
	KeysCount  uint32
	PubKey     []byte
	KeyBlocks  []byte
}

type openSSHPrivateKey struct {
	Check1  uint32
	Check2  uint32
	Keytype string
	Rest    []byte `ssh:"rest"`
}

type openSSHEd25519PrivateKey struct {
	Pub     []byte
	Priv    []byte
	Comment string
	Pad     []byte `ssh:"rest"`
}

func OpenSSHPrivateKey(key crypto.PrivateKey, comment string) (*pem.Block, error) {
	var check uint32
	if err := binary.Read(rand.Reader, binary.BigEndian, &check); err != nil {
		return nil, err
	}

	var pk1 openSSHPrivateKey
	pk1.Check1 = check
	pk1.Check2 = check

	var w openSSHEncryptedPrivateKey
	w.KeysCount = 1

	if k, ok := key.(*ed25519.PrivateKey); ok {
		key = *k
	}

	switch k := key.(type) {
	case ed25519.PrivateKey:
		pub, priv := k[32:], k
		key := openSSHEd25519PrivateKey{
			Pub:     pub,
			Priv:    priv,
			Comment: comment,
		}

		pk1.Keytype = ssh.KeyAlgoED25519
		pk1.Rest = ssh.Marshal(key)

		w.PubKey = ssh.Marshal(struct {
			KeyType string
			Pub     []byte
		}{
			ssh.KeyAlgoED25519, pub,
		})
	default:
		return nil, fmt.Errorf("ssh: unknown key type %T", k)
	}

	w.KeyBlocks = openSSHPadding(ssh.Marshal(pk1), 8)

	w.CipherName, w.KDFName, w.KDFOptions = "none", "none", ""

	return &pem.Block{
		Type:  "OPENSSH PRIVATE KEY",
		Bytes: append([]byte(privateKeyAuthMagic), ssh.Marshal(w)...),
	}, nil
}

func openSSHPadding(block []byte, blocksize int) []byte {
	for i, j := 0, len(block); (j+i)%blocksize != 0; i++ {
		block = append(block, byte(i+1))
	}

	return block
}