This adds a NewSignerFromSigner to crypto/ssh which takes a crypto.Signer and turns it into an ssh.Signer, helpful if, e.g., your crypto.Signer is backed by some sort of hardware device. The interfaces are very similar - the biggest differences are that a crypto.Signer accepts hashed data, while an ssh.Signer does not, and some differences in encoding for DSA and ECDSA signatures. This also adjusts NewSignerFromKey to use NewSignerFromSigner where possible, dropping the rsaPrivateKey and ecdsaPrivateKey types in favor of wrappedSigner. (However, because *dsa.PrivateKey is not a crypto.Signer, we still have to keep dsaPrivateKey) Change-Id: Ia2e20ece9c9d3844b4e5a64c1a7d997178ec8781 Reviewed-on: https://go-review.googlesource.com/10953 Reviewed-by: Adam Langley <agl@golang.org>
647 lines
15 KiB
Go
647 lines
15 KiB
Go
// 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.
|
|
|
|
package ssh
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto"
|
|
"crypto/dsa"
|
|
"crypto/ecdsa"
|
|
"crypto/elliptic"
|
|
"crypto/rsa"
|
|
"crypto/x509"
|
|
"encoding/asn1"
|
|
"encoding/base64"
|
|
"encoding/pem"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"math/big"
|
|
)
|
|
|
|
// These constants represent the algorithm names for key types supported by this
|
|
// package.
|
|
const (
|
|
KeyAlgoRSA = "ssh-rsa"
|
|
KeyAlgoDSA = "ssh-dss"
|
|
KeyAlgoECDSA256 = "ecdsa-sha2-nistp256"
|
|
KeyAlgoECDSA384 = "ecdsa-sha2-nistp384"
|
|
KeyAlgoECDSA521 = "ecdsa-sha2-nistp521"
|
|
)
|
|
|
|
// parsePubKey parses a public key of the given algorithm.
|
|
// Use ParsePublicKey for keys with prepended algorithm.
|
|
func parsePubKey(in []byte, algo string) (pubKey PublicKey, rest []byte, err error) {
|
|
switch algo {
|
|
case KeyAlgoRSA:
|
|
return parseRSA(in)
|
|
case KeyAlgoDSA:
|
|
return parseDSA(in)
|
|
case KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521:
|
|
return parseECDSA(in)
|
|
case CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01, CertAlgoECDSA384v01, CertAlgoECDSA521v01:
|
|
cert, err := parseCert(in, certToPrivAlgo(algo))
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
return cert, nil, nil
|
|
}
|
|
return nil, nil, fmt.Errorf("ssh: unknown key algorithm: %v", err)
|
|
}
|
|
|
|
// parseAuthorizedKey parses a public key in OpenSSH authorized_keys format
|
|
// (see sshd(8) manual page) once the options and key type fields have been
|
|
// removed.
|
|
func parseAuthorizedKey(in []byte) (out PublicKey, comment string, err error) {
|
|
in = bytes.TrimSpace(in)
|
|
|
|
i := bytes.IndexAny(in, " \t")
|
|
if i == -1 {
|
|
i = len(in)
|
|
}
|
|
base64Key := in[:i]
|
|
|
|
key := make([]byte, base64.StdEncoding.DecodedLen(len(base64Key)))
|
|
n, err := base64.StdEncoding.Decode(key, base64Key)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
key = key[:n]
|
|
out, err = ParsePublicKey(key)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
comment = string(bytes.TrimSpace(in[i:]))
|
|
return out, comment, nil
|
|
}
|
|
|
|
// ParseAuthorizedKeys parses a public key from an authorized_keys
|
|
// file used in OpenSSH according to the sshd(8) manual page.
|
|
func ParseAuthorizedKey(in []byte) (out PublicKey, comment string, options []string, rest []byte, err error) {
|
|
for len(in) > 0 {
|
|
end := bytes.IndexByte(in, '\n')
|
|
if end != -1 {
|
|
rest = in[end+1:]
|
|
in = in[:end]
|
|
} else {
|
|
rest = nil
|
|
}
|
|
|
|
end = bytes.IndexByte(in, '\r')
|
|
if end != -1 {
|
|
in = in[:end]
|
|
}
|
|
|
|
in = bytes.TrimSpace(in)
|
|
if len(in) == 0 || in[0] == '#' {
|
|
in = rest
|
|
continue
|
|
}
|
|
|
|
i := bytes.IndexAny(in, " \t")
|
|
if i == -1 {
|
|
in = rest
|
|
continue
|
|
}
|
|
|
|
if out, comment, err = parseAuthorizedKey(in[i:]); err == nil {
|
|
return out, comment, options, rest, nil
|
|
}
|
|
|
|
// No key type recognised. Maybe there's an options field at
|
|
// the beginning.
|
|
var b byte
|
|
inQuote := false
|
|
var candidateOptions []string
|
|
optionStart := 0
|
|
for i, b = range in {
|
|
isEnd := !inQuote && (b == ' ' || b == '\t')
|
|
if (b == ',' && !inQuote) || isEnd {
|
|
if i-optionStart > 0 {
|
|
candidateOptions = append(candidateOptions, string(in[optionStart:i]))
|
|
}
|
|
optionStart = i + 1
|
|
}
|
|
if isEnd {
|
|
break
|
|
}
|
|
if b == '"' && (i == 0 || (i > 0 && in[i-1] != '\\')) {
|
|
inQuote = !inQuote
|
|
}
|
|
}
|
|
for i < len(in) && (in[i] == ' ' || in[i] == '\t') {
|
|
i++
|
|
}
|
|
if i == len(in) {
|
|
// Invalid line: unmatched quote
|
|
in = rest
|
|
continue
|
|
}
|
|
|
|
in = in[i:]
|
|
i = bytes.IndexAny(in, " \t")
|
|
if i == -1 {
|
|
in = rest
|
|
continue
|
|
}
|
|
|
|
if out, comment, err = parseAuthorizedKey(in[i:]); err == nil {
|
|
options = candidateOptions
|
|
return out, comment, options, rest, nil
|
|
}
|
|
|
|
in = rest
|
|
continue
|
|
}
|
|
|
|
return nil, "", nil, nil, errors.New("ssh: no key found")
|
|
}
|
|
|
|
// ParsePublicKey parses an SSH public key formatted for use in
|
|
// the SSH wire protocol according to RFC 4253, section 6.6.
|
|
func ParsePublicKey(in []byte) (out PublicKey, err error) {
|
|
algo, in, ok := parseString(in)
|
|
if !ok {
|
|
return nil, errShortRead
|
|
}
|
|
var rest []byte
|
|
out, rest, err = parsePubKey(in, string(algo))
|
|
if len(rest) > 0 {
|
|
return nil, errors.New("ssh: trailing junk in public key")
|
|
}
|
|
|
|
return out, err
|
|
}
|
|
|
|
// MarshalAuthorizedKey serializes key for inclusion in an OpenSSH
|
|
// authorized_keys file. The return value ends with newline.
|
|
func MarshalAuthorizedKey(key PublicKey) []byte {
|
|
b := &bytes.Buffer{}
|
|
b.WriteString(key.Type())
|
|
b.WriteByte(' ')
|
|
e := base64.NewEncoder(base64.StdEncoding, b)
|
|
e.Write(key.Marshal())
|
|
e.Close()
|
|
b.WriteByte('\n')
|
|
return b.Bytes()
|
|
}
|
|
|
|
// PublicKey is an abstraction of different types of public keys.
|
|
type PublicKey interface {
|
|
// Type returns the key's type, e.g. "ssh-rsa".
|
|
Type() string
|
|
|
|
// Marshal returns the serialized key data in SSH wire format,
|
|
// with the name prefix.
|
|
Marshal() []byte
|
|
|
|
// Verify that sig is a signature on the given data using this
|
|
// key. This function will hash the data appropriately first.
|
|
Verify(data []byte, sig *Signature) error
|
|
}
|
|
|
|
// A Signer can create signatures that verify against a public key.
|
|
type Signer interface {
|
|
// PublicKey returns an associated PublicKey instance.
|
|
PublicKey() PublicKey
|
|
|
|
// Sign returns raw signature for the given data. This method
|
|
// will apply the hash specified for the keytype to the data.
|
|
Sign(rand io.Reader, data []byte) (*Signature, error)
|
|
}
|
|
|
|
type rsaPublicKey rsa.PublicKey
|
|
|
|
func (r *rsaPublicKey) Type() string {
|
|
return "ssh-rsa"
|
|
}
|
|
|
|
// parseRSA parses an RSA key according to RFC 4253, section 6.6.
|
|
func parseRSA(in []byte) (out PublicKey, rest []byte, err error) {
|
|
var w struct {
|
|
E *big.Int
|
|
N *big.Int
|
|
Rest []byte `ssh:"rest"`
|
|
}
|
|
if err := Unmarshal(in, &w); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
if w.E.BitLen() > 24 {
|
|
return nil, nil, errors.New("ssh: exponent too large")
|
|
}
|
|
e := w.E.Int64()
|
|
if e < 3 || e&1 == 0 {
|
|
return nil, nil, errors.New("ssh: incorrect exponent")
|
|
}
|
|
|
|
var key rsa.PublicKey
|
|
key.E = int(e)
|
|
key.N = w.N
|
|
return (*rsaPublicKey)(&key), w.Rest, nil
|
|
}
|
|
|
|
func (r *rsaPublicKey) Marshal() []byte {
|
|
e := new(big.Int).SetInt64(int64(r.E))
|
|
wirekey := struct {
|
|
Name string
|
|
E *big.Int
|
|
N *big.Int
|
|
}{
|
|
KeyAlgoRSA,
|
|
e,
|
|
r.N,
|
|
}
|
|
return Marshal(&wirekey)
|
|
}
|
|
|
|
func (r *rsaPublicKey) Verify(data []byte, sig *Signature) error {
|
|
if sig.Format != r.Type() {
|
|
return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, r.Type())
|
|
}
|
|
h := crypto.SHA1.New()
|
|
h.Write(data)
|
|
digest := h.Sum(nil)
|
|
return rsa.VerifyPKCS1v15((*rsa.PublicKey)(r), crypto.SHA1, digest, sig.Blob)
|
|
}
|
|
|
|
type dsaPublicKey dsa.PublicKey
|
|
|
|
func (r *dsaPublicKey) Type() string {
|
|
return "ssh-dss"
|
|
}
|
|
|
|
// parseDSA parses an DSA key according to RFC 4253, section 6.6.
|
|
func parseDSA(in []byte) (out PublicKey, rest []byte, err error) {
|
|
var w struct {
|
|
P, Q, G, Y *big.Int
|
|
Rest []byte `ssh:"rest"`
|
|
}
|
|
if err := Unmarshal(in, &w); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
key := &dsaPublicKey{
|
|
Parameters: dsa.Parameters{
|
|
P: w.P,
|
|
Q: w.Q,
|
|
G: w.G,
|
|
},
|
|
Y: w.Y,
|
|
}
|
|
return key, w.Rest, nil
|
|
}
|
|
|
|
func (k *dsaPublicKey) Marshal() []byte {
|
|
w := struct {
|
|
Name string
|
|
P, Q, G, Y *big.Int
|
|
}{
|
|
k.Type(),
|
|
k.P,
|
|
k.Q,
|
|
k.G,
|
|
k.Y,
|
|
}
|
|
|
|
return Marshal(&w)
|
|
}
|
|
|
|
func (k *dsaPublicKey) Verify(data []byte, sig *Signature) error {
|
|
if sig.Format != k.Type() {
|
|
return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, k.Type())
|
|
}
|
|
h := crypto.SHA1.New()
|
|
h.Write(data)
|
|
digest := h.Sum(nil)
|
|
|
|
// Per RFC 4253, section 6.6,
|
|
// The value for 'dss_signature_blob' is encoded as a string containing
|
|
// r, followed by s (which are 160-bit integers, without lengths or
|
|
// padding, unsigned, and in network byte order).
|
|
// For DSS purposes, sig.Blob should be exactly 40 bytes in length.
|
|
if len(sig.Blob) != 40 {
|
|
return errors.New("ssh: DSA signature parse error")
|
|
}
|
|
r := new(big.Int).SetBytes(sig.Blob[:20])
|
|
s := new(big.Int).SetBytes(sig.Blob[20:])
|
|
if dsa.Verify((*dsa.PublicKey)(k), digest, r, s) {
|
|
return nil
|
|
}
|
|
return errors.New("ssh: signature did not verify")
|
|
}
|
|
|
|
type dsaPrivateKey struct {
|
|
*dsa.PrivateKey
|
|
}
|
|
|
|
func (k *dsaPrivateKey) PublicKey() PublicKey {
|
|
return (*dsaPublicKey)(&k.PrivateKey.PublicKey)
|
|
}
|
|
|
|
func (k *dsaPrivateKey) Sign(rand io.Reader, data []byte) (*Signature, error) {
|
|
h := crypto.SHA1.New()
|
|
h.Write(data)
|
|
digest := h.Sum(nil)
|
|
r, s, err := dsa.Sign(rand, k.PrivateKey, digest)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sig := make([]byte, 40)
|
|
rb := r.Bytes()
|
|
sb := s.Bytes()
|
|
|
|
copy(sig[20-len(rb):20], rb)
|
|
copy(sig[40-len(sb):], sb)
|
|
|
|
return &Signature{
|
|
Format: k.PublicKey().Type(),
|
|
Blob: sig,
|
|
}, nil
|
|
}
|
|
|
|
type ecdsaPublicKey ecdsa.PublicKey
|
|
|
|
func (key *ecdsaPublicKey) Type() string {
|
|
return "ecdsa-sha2-" + key.nistID()
|
|
}
|
|
|
|
func (key *ecdsaPublicKey) nistID() string {
|
|
switch key.Params().BitSize {
|
|
case 256:
|
|
return "nistp256"
|
|
case 384:
|
|
return "nistp384"
|
|
case 521:
|
|
return "nistp521"
|
|
}
|
|
panic("ssh: unsupported ecdsa key size")
|
|
}
|
|
|
|
func supportedEllipticCurve(curve elliptic.Curve) bool {
|
|
return curve == elliptic.P256() || curve == elliptic.P384() || curve == elliptic.P521()
|
|
}
|
|
|
|
// ecHash returns the hash to match the given elliptic curve, see RFC
|
|
// 5656, section 6.2.1
|
|
func ecHash(curve elliptic.Curve) crypto.Hash {
|
|
bitSize := curve.Params().BitSize
|
|
switch {
|
|
case bitSize <= 256:
|
|
return crypto.SHA256
|
|
case bitSize <= 384:
|
|
return crypto.SHA384
|
|
}
|
|
return crypto.SHA512
|
|
}
|
|
|
|
// parseECDSA parses an ECDSA key according to RFC 5656, section 3.1.
|
|
func parseECDSA(in []byte) (out PublicKey, rest []byte, err error) {
|
|
var w struct {
|
|
Curve string
|
|
KeyBytes []byte
|
|
Rest []byte `ssh:"rest"`
|
|
}
|
|
|
|
if err := Unmarshal(in, &w); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
key := new(ecdsa.PublicKey)
|
|
|
|
switch w.Curve {
|
|
case "nistp256":
|
|
key.Curve = elliptic.P256()
|
|
case "nistp384":
|
|
key.Curve = elliptic.P384()
|
|
case "nistp521":
|
|
key.Curve = elliptic.P521()
|
|
default:
|
|
return nil, nil, errors.New("ssh: unsupported curve")
|
|
}
|
|
|
|
key.X, key.Y = elliptic.Unmarshal(key.Curve, w.KeyBytes)
|
|
if key.X == nil || key.Y == nil {
|
|
return nil, nil, errors.New("ssh: invalid curve point")
|
|
}
|
|
return (*ecdsaPublicKey)(key), w.Rest, nil
|
|
}
|
|
|
|
func (key *ecdsaPublicKey) Marshal() []byte {
|
|
// See RFC 5656, section 3.1.
|
|
keyBytes := elliptic.Marshal(key.Curve, key.X, key.Y)
|
|
w := struct {
|
|
Name string
|
|
ID string
|
|
Key []byte
|
|
}{
|
|
key.Type(),
|
|
key.nistID(),
|
|
keyBytes,
|
|
}
|
|
|
|
return Marshal(&w)
|
|
}
|
|
|
|
func (key *ecdsaPublicKey) Verify(data []byte, sig *Signature) error {
|
|
if sig.Format != key.Type() {
|
|
return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, key.Type())
|
|
}
|
|
|
|
h := ecHash(key.Curve).New()
|
|
h.Write(data)
|
|
digest := h.Sum(nil)
|
|
|
|
// Per RFC 5656, section 3.1.2,
|
|
// The ecdsa_signature_blob value has the following specific encoding:
|
|
// mpint r
|
|
// mpint s
|
|
var ecSig struct {
|
|
R *big.Int
|
|
S *big.Int
|
|
}
|
|
|
|
if err := Unmarshal(sig.Blob, &ecSig); err != nil {
|
|
return err
|
|
}
|
|
|
|
if ecdsa.Verify((*ecdsa.PublicKey)(key), digest, ecSig.R, ecSig.S) {
|
|
return nil
|
|
}
|
|
return errors.New("ssh: signature did not verify")
|
|
}
|
|
|
|
// NewSignerFromKey takes an *rsa.PrivateKey, *dsa.PrivateKey,
|
|
// *ecdsa.PrivateKey or any other crypto.Signer and returns a corresponding
|
|
// Signer instance. ECDSA keys must use P-256, P-384 or P-521.
|
|
func NewSignerFromKey(key interface{}) (Signer, error) {
|
|
switch key := key.(type) {
|
|
case crypto.Signer:
|
|
return NewSignerFromSigner(key)
|
|
case *dsa.PrivateKey:
|
|
return &dsaPrivateKey{key}, nil
|
|
default:
|
|
return nil, fmt.Errorf("ssh: unsupported key type %T", key)
|
|
}
|
|
}
|
|
|
|
type wrappedSigner struct {
|
|
signer crypto.Signer
|
|
pubKey PublicKey
|
|
}
|
|
|
|
// NewSignerFromSigner takes any crypto.Signer implementation and
|
|
// returns a corresponding Signer interface. This can be used, for
|
|
// example, with keys kept in hardware modules.
|
|
func NewSignerFromSigner(signer crypto.Signer) (Signer, error) {
|
|
pubKey, err := NewPublicKey(signer.Public())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &wrappedSigner{signer, pubKey}, nil
|
|
}
|
|
|
|
func (s *wrappedSigner) PublicKey() PublicKey {
|
|
return s.pubKey
|
|
}
|
|
|
|
func (s *wrappedSigner) Sign(rand io.Reader, data []byte) (*Signature, error) {
|
|
var hashFunc crypto.Hash
|
|
|
|
switch key := s.pubKey.(type) {
|
|
case *rsaPublicKey, *dsaPublicKey:
|
|
hashFunc = crypto.SHA1
|
|
case *ecdsaPublicKey:
|
|
hashFunc = ecHash(key.Curve)
|
|
default:
|
|
return nil, fmt.Errorf("ssh: unsupported key type %T", key)
|
|
}
|
|
|
|
h := hashFunc.New()
|
|
h.Write(data)
|
|
digest := h.Sum(nil)
|
|
|
|
signature, err := s.signer.Sign(rand, digest, hashFunc)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// crypto.Signer.Sign is expected to return an ASN.1-encoded signature
|
|
// for ECDSA and DSA, but that's not the encoding expected by SSH, so
|
|
// re-encode.
|
|
switch s.pubKey.(type) {
|
|
case *ecdsaPublicKey, *dsaPublicKey:
|
|
type asn1Signature struct {
|
|
R, S *big.Int
|
|
}
|
|
asn1Sig := new(asn1Signature)
|
|
_, err := asn1.Unmarshal(signature, asn1Sig)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
switch s.pubKey.(type) {
|
|
case *ecdsaPublicKey:
|
|
signature = Marshal(asn1Sig)
|
|
|
|
case *dsaPublicKey:
|
|
signature = make([]byte, 40)
|
|
r := asn1Sig.R.Bytes()
|
|
s := asn1Sig.S.Bytes()
|
|
copy(signature[20-len(r):20], r)
|
|
copy(signature[40-len(s):40], s)
|
|
}
|
|
}
|
|
|
|
return &Signature{
|
|
Format: s.pubKey.Type(),
|
|
Blob: signature,
|
|
}, nil
|
|
}
|
|
|
|
// NewPublicKey takes an *rsa.PublicKey, *dsa.PublicKey, *ecdsa.PublicKey or
|
|
// any other crypto.Signer and returns a corresponding Signer instance. ECDSA
|
|
// keys must use P-256, P-384 or P-521.
|
|
func NewPublicKey(key interface{}) (PublicKey, error) {
|
|
switch key := key.(type) {
|
|
case *rsa.PublicKey:
|
|
return (*rsaPublicKey)(key), nil
|
|
case *ecdsa.PublicKey:
|
|
if !supportedEllipticCurve(key.Curve) {
|
|
return nil, errors.New("ssh: only P-256, P-384 and P-521 EC keys are supported.")
|
|
}
|
|
return (*ecdsaPublicKey)(key), nil
|
|
case *dsa.PublicKey:
|
|
return (*dsaPublicKey)(key), nil
|
|
default:
|
|
return nil, fmt.Errorf("ssh: unsupported key type %T", key)
|
|
}
|
|
}
|
|
|
|
// ParsePrivateKey returns a Signer from a PEM encoded private key. It supports
|
|
// the same keys as ParseRawPrivateKey.
|
|
func ParsePrivateKey(pemBytes []byte) (Signer, error) {
|
|
key, err := ParseRawPrivateKey(pemBytes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return NewSignerFromKey(key)
|
|
}
|
|
|
|
// ParseRawPrivateKey returns a private key from a PEM encoded private key. It
|
|
// supports RSA (PKCS#1), DSA (OpenSSL), and ECDSA private keys.
|
|
func ParseRawPrivateKey(pemBytes []byte) (interface{}, error) {
|
|
block, _ := pem.Decode(pemBytes)
|
|
if block == nil {
|
|
return nil, errors.New("ssh: no key found")
|
|
}
|
|
|
|
switch block.Type {
|
|
case "RSA PRIVATE KEY":
|
|
return x509.ParsePKCS1PrivateKey(block.Bytes)
|
|
case "EC PRIVATE KEY":
|
|
return x509.ParseECPrivateKey(block.Bytes)
|
|
case "DSA PRIVATE KEY":
|
|
return ParseDSAPrivateKey(block.Bytes)
|
|
default:
|
|
return nil, fmt.Errorf("ssh: unsupported key type %q", block.Type)
|
|
}
|
|
}
|
|
|
|
// ParseDSAPrivateKey returns a DSA private key from its ASN.1 DER encoding, as
|
|
// specified by the OpenSSL DSA man page.
|
|
func ParseDSAPrivateKey(der []byte) (*dsa.PrivateKey, error) {
|
|
var k struct {
|
|
Version int
|
|
P *big.Int
|
|
Q *big.Int
|
|
G *big.Int
|
|
Priv *big.Int
|
|
Pub *big.Int
|
|
}
|
|
rest, err := asn1.Unmarshal(der, &k)
|
|
if err != nil {
|
|
return nil, errors.New("ssh: failed to parse DSA key: " + err.Error())
|
|
}
|
|
if len(rest) > 0 {
|
|
return nil, errors.New("ssh: garbage after DSA key")
|
|
}
|
|
|
|
return &dsa.PrivateKey{
|
|
PublicKey: dsa.PublicKey{
|
|
Parameters: dsa.Parameters{
|
|
P: k.P,
|
|
Q: k.Q,
|
|
G: k.G,
|
|
},
|
|
Y: k.Priv,
|
|
},
|
|
X: k.Pub,
|
|
}, nil
|
|
}
|