pax_global_header00006660000000000000000000000064147614253230014521gustar00rootroot0000000000000052 comment=71afa803590f258544eb36010806fe568f1eea69 sshkeys-1.4.0/000077500000000000000000000000001476142532300132145ustar00rootroot00000000000000sshkeys-1.4.0/.gitignore000066400000000000000000000000101476142532300151730ustar00rootroot00000000000000/vendor sshkeys-1.4.0/.travis.yml000066400000000000000000000002161476142532300153240ustar00rootroot00000000000000language: go sudo: false go_import_path: github.com/ScaleFT/sshkeys go: - "1.13.x" env: - GO111MODULE=on script: - go test -v ./...sshkeys-1.4.0/CODE_OF_CONDUCT.md000066400000000000000000000062771476142532300160270ustar00rootroot00000000000000# Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [opensource@scaleft.com](mailto:opensource@scaleft.com). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ sshkeys-1.4.0/CONTRIBUTING.md000066400000000000000000000031511476142532300154450ustar00rootroot00000000000000# How to Contribute ScaleFT's projects are [Apache 2.0 licensed](LICENSE) and accept contributions via GitHub pull requests. This document outlines some of the conventions on development workflow, contact points, community conduct and other resources to make it easier to get your contribution accepted. # Code of Conduct This project adheres to the Contributor Covenant [code of conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to [opensource@scaleft.com](mailto:opensource@scaleft.com). # Reporting Security Issues ScaleFT takes security seriously. If you discover a security issue, please bring it to our attention right away! Please DO NOT file a public issue or pull request, [instead send your report privately to the ScaleFT Security Team](https://www.scaleft.com/company/security/), reachable at [security@scaleft.com](mailto:security@scaleft.com). Security reports are greatly appreciated and we will publicly thank you for them. # Getting Started - Fork the repository on GitHub - Read the [README](README.md) for build and test instructions - Play with the project, submit bugs, submit patches! # Contribution Flow This is a rough outline of what a contributor's workflow looks like: - Create a topic branch from where you want to base your work (usually master). - Make commits of logical units, rebasing later is ok too! - Push your changes to a topic branch in your fork of the repository. - Make sure the tests pass, and add any new tests as appropriate. - Submit a pull request to the original repository. Thanks for your contributions! sshkeys-1.4.0/LICENSE000066400000000000000000000261361476142532300142310ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. sshkeys-1.4.0/NOTICE000066400000000000000000000004311476142532300141160ustar00rootroot00000000000000sshkeys Copyright 2017 ScaleFT, Inc This product includes software developed at ScaleFT, Inc. (https://www.scaleft.com/). Portions of this software are derived from https://github.com/golang/crypto/blob/master/ssh/keys.go Copyright (c) 2009 The Go Authors. All rights reserved. sshkeys-1.4.0/README.md000066400000000000000000000015671476142532300145040ustar00rootroot00000000000000# sshkeys [![GoDoc](https://godoc.org/github.com/ScaleFT/sshkeys?status.svg)](https://godoc.org/github.com/ScaleFT/sshkeys) [![Build Status](https://travis-ci.org/ScaleFT/sshkeys.svg?branch=master)](https://travis-ci.org/ScaleFT/sshkeys) `sshkeys` provides utilities for parsing and marshalling cryptographic keys used for SSH, in both cleartext and encrypted formats. [ssh.ParseRawPrivateKey](https://godoc.org/golang.org/x/crypto/ssh#ParseRawPrivateKey) only supports parsing a subset of the formats `sshkeys` supports, does not support parsing encrypted private keys, and does not support marshalling. ## Supported Formats * OpenSSH's [PROTOCOL.key](https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key) for RSA and ED25519 keys. * OpenSSH version >= 7.6 using aes256-ctr encryption * "Classic" PEM containing RSA (PKCS#1), DSA (OpenSSL), and ECDSA private keys. sshkeys-1.4.0/go.mod000066400000000000000000000005671476142532300143320ustar00rootroot00000000000000module github.com/ScaleFT/sshkeys go 1.23.0 require ( github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a github.com/stretchr/testify v1.5.1 golang.org/x/crypto v0.35.0 ) require ( github.com/davecgh/go-spew v1.1.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect golang.org/x/sys v0.30.0 // indirect gopkg.in/yaml.v2 v2.2.2 // indirect ) sshkeys-1.4.0/go.sum000066400000000000000000000032061476142532300143500ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a h1:saTgr5tMLFnmy/yg3qDTft4rE5DY2uJ/cCxCe3q0XTU= github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a/go.mod h1:Bw9BbhOJVNR+t0jCqx2GC6zv0TGBsShs56Y3gfSCvl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= sshkeys-1.4.0/marshal.go000066400000000000000000000146511476142532300152010ustar00rootroot00000000000000package sshkeys import ( "crypto/aes" "crypto/cipher" "crypto/dsa" "crypto/ecdsa" "crypto/rand" "crypto/rsa" "crypto/x509" "encoding/asn1" "encoding/pem" "fmt" "math" "math/big" "github.com/dchest/bcrypt_pbkdf" "golang.org/x/crypto/ed25519" "golang.org/x/crypto/ssh" ) const keySizeAES256 = 32 // Format of private key to use when Marshaling. type Format int const ( // FormatOpenSSHv1 encodes a private key using OpenSSH's PROTOCOL.key format: https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key FormatOpenSSHv1 Format = iota // FormatClassicPEM encodes private keys in PEM, with a key-specific encoding, as used by OpenSSH. FormatClassicPEM ) // MarshalOptions provides the Marshal function format and encryption options. type MarshalOptions struct { // Passphrase to encrypt private key with, if nil, the key will not be encrypted. Passphrase []byte // Format to encode the private key in. Format Format } // Marshal converts a private key into an optionally encrypted format. func Marshal(pk interface{}, opts *MarshalOptions) ([]byte, error) { switch opts.Format { case FormatOpenSSHv1: return marshalOpenssh(pk, opts) case FormatClassicPEM: return marshalPem(pk, opts) default: return nil, fmt.Errorf("sshkeys: invalid format %d", opts.Format) } } func marshalPem(pk interface{}, opts *MarshalOptions) ([]byte, error) { var err error var plain []byte var pemType string switch key := pk.(type) { case *rsa.PrivateKey: pemType = "RSA PRIVATE KEY" plain = x509.MarshalPKCS1PrivateKey(key) case *ecdsa.PrivateKey: pemType = "EC PRIVATE KEY" plain, err = x509.MarshalECPrivateKey(key) if err != nil { return nil, err } case *dsa.PrivateKey: pemType = "DSA PRIVATE KEY" plain, err = marshalDSAPrivateKey(key) if err != nil { return nil, err } case *ed25519.PrivateKey: return nil, fmt.Errorf("sshkeys: ed25519 keys must be marshaled with FormatOpenSSHv1") default: return nil, fmt.Errorf("sshkeys: unsupported key type %T", pk) } if len(opts.Passphrase) > 0 { block, err := x509.EncryptPEMBlock(rand.Reader, pemType, plain, opts.Passphrase, x509.PEMCipherAES128) if err != nil { return nil, err } return pem.EncodeToMemory(block), nil } return pem.EncodeToMemory(&pem.Block{ Type: pemType, Bytes: plain, }), nil } type dsaOpenssl struct { Version int P *big.Int Q *big.Int G *big.Int Pub *big.Int Priv *big.Int } // https://github.com/golang/crypto/blob/master/ssh/keys.go#L793-L804 func marshalDSAPrivateKey(pk *dsa.PrivateKey) ([]byte, error) { k := dsaOpenssl{ Version: 0, P: pk.P, Q: pk.Q, G: pk.G, Pub: pk.Y, Priv: pk.X, } return asn1.Marshal(k) } const opensshv1Magic = "openssh-key-v1" type opensshHeader struct { CipherName string KdfName string KdfOpts string NumKeys uint32 PubKey string PrivKeyBlock string } type opensshKey struct { Check1 uint32 Check2 uint32 Keytype string Rest []byte `ssh:"rest"` } type opensshRsa struct { N *big.Int E *big.Int D *big.Int Iqmp *big.Int P *big.Int Q *big.Int Comment string Pad []byte `ssh:"rest"` } type opensshED25519 struct { Pub []byte Priv []byte Comment string Pad []byte `ssh:"rest"` } func padBytes(data []byte, blocksize int) []byte { if blocksize != 0 { var i byte for i = byte(1); len(data)%blocksize != 0; i++ { data = append(data, i&0xFF) } } return data } func marshalOpenssh(pk interface{}, opts *MarshalOptions) ([]byte, error) { var blocksize int var keylen int out := opensshHeader{ CipherName: "none", KdfName: "none", KdfOpts: "", NumKeys: 1, PubKey: "", } if len(opts.Passphrase) > 0 { out.CipherName = "aes256-cbc" out.KdfName = "bcrypt" keylen = keySizeAES256 blocksize = aes.BlockSize } // Get a crypto rand in the range [0, uint32-max] randnum, err := rand.Int(rand.Reader, big.NewInt(math.MaxUint32+1)) if err != nil { return nil, fmt.Errorf("sshkeys: failed to get random number: %w", err) } // Convert it to uint32. As the content was within uint32 limits, nothing should be lost check := uint32(randnum.Uint64()) pk1 := opensshKey{ Check1: check, Check2: check, } switch key := pk.(type) { case *rsa.PrivateKey: k := &opensshRsa{ N: key.N, E: big.NewInt(int64(key.E)), D: key.D, Iqmp: key.Precomputed.Qinv, P: key.Primes[0], Q: key.Primes[1], Comment: "", } data := ssh.Marshal(k) pk1.Keytype = ssh.KeyAlgoRSA pk1.Rest = data publicKey, err := ssh.NewPublicKey(&key.PublicKey) if err != nil { return nil, err } out.PubKey = string(publicKey.Marshal()) case ed25519.PrivateKey: k := opensshED25519{ Pub: key.Public().(ed25519.PublicKey), Priv: key, } data := ssh.Marshal(k) pk1.Keytype = ssh.KeyAlgoED25519 pk1.Rest = data publicKey, err := ssh.NewPublicKey(key.Public()) if err != nil { return nil, err } out.PubKey = string(publicKey.Marshal()) case *ed25519.PrivateKey: k := opensshED25519{ Pub: key.Public().(ed25519.PublicKey), Priv: *key, } data := ssh.Marshal(k) pk1.Keytype = ssh.KeyAlgoED25519 pk1.Rest = data publicKey, err := ssh.NewPublicKey(key.Public()) if err != nil { return nil, err } out.PubKey = string(publicKey.Marshal()) default: return nil, fmt.Errorf("sshkeys: unsupported key type %T", pk) } if len(opts.Passphrase) > 0 { rounds := 16 ivlen := blocksize salt := make([]byte, blocksize) _, err := rand.Read(salt) if err != nil { return nil, err } kdfdata, err := bcrypt_pbkdf.Key(opts.Passphrase, salt, rounds, keylen+ivlen) if err != nil { return nil, err } iv := kdfdata[keylen : ivlen+keylen] aeskey := kdfdata[0:keylen] block, err := aes.NewCipher(aeskey) if err != nil { return nil, err } pkblock := padBytes(ssh.Marshal(pk1), blocksize) cbc := cipher.NewCBCEncrypter(block, iv) cbc.CryptBlocks(pkblock, pkblock) out.PrivKeyBlock = string(pkblock) var opts struct { Salt []byte Rounds uint32 } opts.Salt = salt opts.Rounds = uint32(rounds) out.KdfOpts = string(ssh.Marshal(&opts)) } else { out.PrivKeyBlock = string(ssh.Marshal(pk1)) } outBytes := []byte(opensshv1Magic) outBytes = append(outBytes, 0) outBytes = append(outBytes, ssh.Marshal(out)...) block := &pem.Block{ Type: "OPENSSH PRIVATE KEY", Bytes: outBytes, } return pem.EncodeToMemory(block), nil } sshkeys-1.4.0/marshal_test.go000066400000000000000000000053121476142532300162320ustar00rootroot00000000000000package sshkeys_test import ( "crypto/rand" "testing" "github.com/ScaleFT/sshkeys" "github.com/ScaleFT/sshkeys/testdata" "github.com/stretchr/testify/require" "golang.org/x/crypto/ssh" ) func testSigners(t *testing.T, name string, a ssh.Signer, b ssh.Signer) { require.Equal(t, a.PublicKey().Marshal(), b.PublicKey().Marshal()) sign := []byte("hello world") sig, err := a.Sign(rand.Reader, sign) require.NoError(t, err, "signer failed for %s", name) err = b.PublicKey().Verify(sign, sig) require.NoError(t, err, "verify failed for %s", name) } func TestMarshalOldFormat(t *testing.T) { password := []byte("gopher") for _, k := range testdata.PEMEncryptedKeys { // ed25519 is only specified in the new format if k.Name == "ed25519-openssh-encrypted-aes256-cbc" || k.Name == "ed25519-openssh-encrypted-aes256-ctr" { continue } t.Run(k.Name, func(t *testing.T) { pk, err := sshkeys.ParseEncryptedRawPrivateKey(k.PEMBytes, []byte(k.EncryptionKey)) require.NoError(t, err, "error parsing %s", k.Name) require.NotNil(t, pk, "nil return from parsing %s", k.Name) signer, err := ssh.NewSignerFromKey(pk) require.NoError(t, err) data, err := sshkeys.Marshal(pk, &sshkeys.MarshalOptions{ Passphrase: password, Format: sshkeys.FormatClassicPEM, }) require.NoError(t, err) require.NotNil(t, data, "nil return from marshaling %s", k.Name) pk2, err := sshkeys.ParseEncryptedRawPrivateKey(data, password) require.NoError(t, err, "error from parsing %s", k.Name) require.NotNil(t, pk2, "nil return from parsing %s", k.Name) signer2, err := ssh.NewSignerFromKey(pk2) require.NoError(t, err) testSigners(t, k.Name, signer, signer2) }) } } func TestMarshalNewFormat(t *testing.T) { password := []byte("gopher") for _, k := range testdata.PEMEncryptedKeys { if k.Name == "dsa-encrypted-aes256-cbc" { continue } t.Run(k.Name, func(t *testing.T) { pk, err := sshkeys.ParseEncryptedRawPrivateKey(k.PEMBytes, []byte(k.EncryptionKey)) require.NoError(t, err, "error parsing %s", k.Name) require.NotNil(t, pk, "nil return from parsing %s", k.Name) signer, err := ssh.NewSignerFromKey(pk) require.NoError(t, err) data, err := sshkeys.Marshal(pk, &sshkeys.MarshalOptions{ Passphrase: password, Format: sshkeys.FormatOpenSSHv1, }) require.NoError(t, err) require.NotNil(t, data, "nil return from marshaling %s", k.Name) pk2, err := sshkeys.ParseEncryptedRawPrivateKey(data, password) require.NoError(t, err, "error from parsing %s", k.Name) require.NotNil(t, pk2, "nil return from parsing %s", k.Name) signer2, err := ssh.NewSignerFromKey(pk2) require.NoError(t, err) testSigners(t, k.Name, signer, signer2) }) } } sshkeys-1.4.0/parse.go000066400000000000000000000026401476142532300146570ustar00rootroot00000000000000// Portions of this file are based on https://github.com/golang/crypto/blob/master/ssh/keys.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 sshkeys import ( "crypto/x509" "golang.org/x/crypto/ssh" ) // ErrIncorrectPassword is returned when the supplied passphrase was not correct for an encrypted private key. var ErrIncorrectPassword = x509.IncorrectPasswordError // ParseEncryptedPrivateKey returns a Signer from an encrypted private key. It supports // the same keys as ParseEncryptedRawPrivateKey. func ParseEncryptedPrivateKey(data []byte, passphrase []byte) (ssh.Signer, error) { key, err := ParseEncryptedRawPrivateKey(data, passphrase) if err != nil { return nil, err } return ssh.NewSignerFromKey(key) } // ParseEncryptedRawPrivateKey returns a private key from an encrypted private key. It // supports RSA (PKCS#1 or OpenSSH), DSA (OpenSSL), and ECDSA private keys. // // ErrIncorrectPassword will be returned if the supplied passphrase is wrong, // but some formats like RSA in PKCS#1 detecting a wrong passphrase is difficult, // and other parse errors may be returned. func ParseEncryptedRawPrivateKey(data []byte, passphrase []byte) (interface{}, error) { if passphrase == nil { return ssh.ParseRawPrivateKey(data) } return ssh.ParseRawPrivateKeyWithPassphrase(data, passphrase) } sshkeys-1.4.0/parse_test.go000066400000000000000000000020661476142532300157200ustar00rootroot00000000000000package sshkeys_test import ( "testing" "github.com/ScaleFT/sshkeys" "github.com/ScaleFT/sshkeys/testdata" "github.com/stretchr/testify/require" ) func TestParse(t *testing.T) { for _, k := range testdata.PEMBytes { t.Run(k.Name, func(t *testing.T) { pk, err := sshkeys.ParseEncryptedPrivateKey(k.PEMBytes, nil) require.NoError(t, err, "error parsing %s", k.Name) require.NotNil(t, pk, "nil return from parsing %s", k.Name) }) } } func TestEncryptedParse(t *testing.T) { wrongKey := []byte("hello world") for _, k := range testdata.PEMEncryptedKeys { t.Run(k.Name, func(t *testing.T) { pk, err := sshkeys.ParseEncryptedPrivateKey(k.PEMBytes, wrongKey) require.Error(t, err, "expected error from %s", k.Name) require.Equal(t, err, sshkeys.ErrIncorrectPassword, "expected error from %s", k.Name) require.Nil(t, pk, "non-nil return from parsing %s", k.Name) pk, err = sshkeys.ParseEncryptedPrivateKey(k.PEMBytes, []byte(k.EncryptionKey)) require.NoError(t, err) require.NotNil(t, pk, "nil return from parsing %s", k.Name) }) } } sshkeys-1.4.0/testdata/000077500000000000000000000000001476142532300150255ustar00rootroot00000000000000sshkeys-1.4.0/testdata/keys.go000066400000000000000000000335761476142532300163450ustar00rootroot00000000000000// Copyright 2014 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 testdata var PEMBytes = []struct { Name string PEMBytes []byte }{ { Name: "dsa", PEMBytes: []byte(`-----BEGIN DSA PRIVATE KEY----- MIIBuwIBAAKBgQD6PDSEyXiI9jfNs97WuM46MSDCYlOqWw80ajN16AohtBncs1YB lHk//dQOvCYOsYaE+gNix2jtoRjwXhDsc25/IqQbU1ahb7mB8/rsaILRGIbA5WH3 EgFtJmXFovDz3if6F6TzvhFpHgJRmLYVR8cqsezL3hEZOvvs2iH7MorkxwIVAJHD nD82+lxh2fb4PMsIiaXudAsBAoGAQRf7Q/iaPRn43ZquUhd6WwvirqUj+tkIu6eV 2nZWYmXLlqFQKEy4Tejl7Wkyzr2OSYvbXLzo7TNxLKoWor6ips0phYPPMyXld14r juhT24CrhOzuLMhDduMDi032wDIZG4Y+K7ElU8Oufn8Sj5Wge8r6ANmmVgmFfynr FhdYCngCgYEA3ucGJ93/Mx4q4eKRDxcWD3QzWyqpbRVRRV1Vmih9Ha/qC994nJFz DQIdjxDIT2Rk2AGzMqFEB68Zc3O+Wcsmz5eWWzEwFxaTwOGWTyDqsDRLm3fD+QYj nOwuxb0Kce+gWI8voWcqC9cyRm09jGzu2Ab3Bhtpg8JJ8L7gS3MRZK4CFEx4UAfY Fmsr0W6fHB9nhS4/UXM8 -----END DSA PRIVATE KEY----- `), }, { Name: "ecdsa", PEMBytes: []byte(`-----BEGIN EC PRIVATE KEY----- MHcCAQEEINGWx0zo6fhJ/0EAfrPzVFyFC9s18lBt3cRoEDhS3ARooAoGCCqGSM49 AwEHoUQDQgAEi9Hdw6KvZcWxfg2IDhA7UkpDtzzt6ZqJXSsFdLd+Kx4S3Sx4cVO+ 6/ZOXRnPmNAlLUqjShUsUBBngG0u2fqEqA== -----END EC PRIVATE KEY----- `), }, { Name: "rsa", PEMBytes: []byte(`-----BEGIN RSA PRIVATE KEY----- MIICXAIBAAKBgQC8A6FGHDiWCSREAXCq6yBfNVr0xCVG2CzvktFNRpue+RXrGs/2 a6ySEJQb3IYquw7HlJgu6fg3WIWhOmHCjfpG0PrL4CRwbqQ2LaPPXhJErWYejcD8 Di00cF3677+G10KMZk9RXbmHtuBFZT98wxg8j+ZsBMqGM1+7yrWUvynswQIDAQAB AoGAJMCk5vqfSRzyXOTXLGIYCuR4Kj6pdsbNSeuuRGfYBeR1F2c/XdFAg7D/8s5R 38p/Ih52/Ty5S8BfJtwtvgVY9ecf/JlU/rl/QzhG8/8KC0NG7KsyXklbQ7gJT8UT Ojmw5QpMk+rKv17ipDVkQQmPaj+gJXYNAHqImke5mm/K/h0CQQDciPmviQ+DOhOq 2ZBqUfH8oXHgFmp7/6pXw80DpMIxgV3CwkxxIVx6a8lVH9bT/AFySJ6vXq4zTuV9 6QmZcZzDAkEA2j/UXJPIs1fQ8z/6sONOkU/BjtoePFIWJlRxdN35cZjXnBraX5UR fFHkePv4YwqmXNqrBOvSu+w2WdSDci+IKwJAcsPRc/jWmsrJW1q3Ha0hSf/WG/Bu X7MPuXaKpP/DkzGoUmb8ks7yqj6XWnYkPNLjCc8izU5vRwIiyWBRf4mxMwJBAILa NDvRS0rjwt6lJGv7zPZoqDc65VfrK2aNyHx2PgFyzwrEOtuF57bu7pnvEIxpLTeM z26i6XVMeYXAWZMTloMCQBbpGgEERQpeUknLBqUHhg/wXF6+lFA+vEGnkY+Dwab2 KCXFGd+SQ5GdUcEMe9isUH6DYj/6/yCDoFrXXmpQb+M= -----END RSA PRIVATE KEY----- `), }, { Name: "user", PEMBytes: []byte(`-----BEGIN EC PRIVATE KEY----- MHcCAQEEILYCAeq8f7V4vSSypRw7pxy8yz3V5W4qg8kSC3zJhqpQoAoGCCqGSM49 AwEHoUQDQgAEYcO2xNKiRUYOLEHM7VYAp57HNyKbOdYtHD83Z4hzNPVC4tM5mdGD PLL8IEwvYu2wq+lpXfGQnNMbzYf9gspG0w== -----END EC PRIVATE KEY----- `), }, { Name: "rsa-openssh-format", PEMBytes: []byte(`-----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAlwAAAAdzc2gtcn NhAAAAAwEAAQAAAIEAwa48yfWFi3uIdqzuf9X7C2Zxfea/Iaaw0zIwHudpF8U92WVIiC5l oEuW1+OaVi3UWfIEjWMV1tHGysrHOwtwc34BPCJqJknUQO/KtDTBTJ4Pryhw1bWPC999Lz a+yrCTdNQYBzoROXKExZgPFh9pTMi5wqpHDuOQ2qZFIEI3lT0AAAIQWL0H31i9B98AAAAH c3NoLXJzYQAAAIEAwa48yfWFi3uIdqzuf9X7C2Zxfea/Iaaw0zIwHudpF8U92WVIiC5loE uW1+OaVi3UWfIEjWMV1tHGysrHOwtwc34BPCJqJknUQO/KtDTBTJ4Pryhw1bWPC999Lza+ yrCTdNQYBzoROXKExZgPFh9pTMi5wqpHDuOQ2qZFIEI3lT0AAAADAQABAAAAgCThyTGsT4 IARDxVMhWl6eiB2ZrgFgWSeJm/NOqtppWgOebsIqPMMg4UVuVFsl422/lE3RkPhVkjGXgE pWvZAdCnmLmApK8wK12vF334lZhZT7t3Z9EzJps88PWEHo7kguf285HcnUM7FlFeissJdk kXly34y7/3X/a6Tclm+iABAAAAQE0xR/KxZ39slwfMv64Rz7WKk1PPskaryI29aHE3mKHk pY2QA+P3QlrKxT/VWUMjHUbNNdYfJm48xu0SGNMRdKMAAABBAORh2NP/06JUV3J9W/2Hju X1ViJuqqcQnJPVzpgSL826EC2xwOECTqoY8uvFpUdD7CtpksIxNVqRIhuNOlz0lqEAAABB ANkaHTTaPojClO0dKJ/Zjs7pWOCGliebBYprQ/Y4r9QLBkC/XaWMS26gFIrjgC7D2Rv+rZ wSD0v0RcmkITP1ZR0AAAAYcHF1ZXJuYUBMdWNreUh5ZHJvLmxvY2FsAQID -----END OPENSSH PRIVATE KEY-----`), }, { Name: "ed25519-openssh-format", PEMBytes: []byte(`-----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW QyNTUxOQAAACA+3f7hS7g5UWwXOGVTrMfhmxyrjqz7Sxxbx7I1j8DvvwAAAJhAFfkOQBX5 DgAAAAtzc2gtZWQyNTUxOQAAACA+3f7hS7g5UWwXOGVTrMfhmxyrjqz7Sxxbx7I1j8Dvvw AAAEAaYmXltfW6nhRo3iWGglRB48lYq0z0Q3I3KyrdutEr6j7d/uFLuDlRbBc4ZVOsx+Gb HKuOrPtLHFvHsjWPwO+/AAAAE2dhcnRvbm1AZ2FydG9ubS14cHMBAg== -----END OPENSSH PRIVATE KEY----- `), }, } var PEMEncryptedKeys = []struct { Name string EncryptionKey string PEMBytes []byte }{ { Name: "rsa-encrypted-aes256-cbc", EncryptionKey: "r54-G0pher_t3st$", PEMBytes: []byte(`-----BEGIN RSA PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: AES-128-CBC,3E1714DE130BC5E81327F36564B05462 MqW88sud4fnWk/Jk3fkjh7ydu51ZkHLN5qlQgA4SkAXORPPMj2XvqZOv1v2LOgUV dUevUn8PZK7a9zbZg4QShUSzwE5k6wdB7XKPyBgI39mJ79GBd2U4W3h6KT6jIdWA goQpluxkrzr2/X602IaxLEre97FT9mpKC6zxKCLvyFWVIP9n3OSFS47cTTXyFr+l 7PdRhe60nn6jSBgUNk/Q1lAvEQ9fufdPwDYY93F1wyJ6lOr0F1+mzRrMbH67NyKs rG8J1Fa7cIIre7ueKIAXTIne7OAWqpU9UDgQatDtZTbvA7ciqGsSFgiwwW13N+Rr hN8MkODKs9cjtONxSKi05s206A3NDU6STtZ3KuPDjFE1gMJODotOuqSM+cxKfyFq wxpk/CHYCDdMAVBSwxb/vraOHamylL4uCHpJdBHypzf2HABt+lS8Su23uAmL87DR yvyCS/lmpuNTndef6qHPRkoW2EV3xqD3ovosGf7kgwGJUk2ZpCLVteqmYehKlZDK r/Jy+J26ooI2jIg9bjvD1PZq+Mv+2dQ1RlDrPG3PB+rEixw6vBaL9x3jatCd4ej7 XG7lb3qO9xFpLsx89tkEcvpGR+broSpUJ6Mu5LBCVmrvqHjvnDhrZVz1brMiQtU9 iMZbgXqDLXHd6ERWygk7OTU03u+l1gs+KGMfmS0h0ZYw6KGVLgMnsoxqd6cFSKNB 8Ohk9ZTZGCiovlXBUepyu8wKat1k8YlHSfIHoRUJRhhcd7DrmojC+bcbMIZBU22T Pl2ftVRGtcQY23lYd0NNKfebF7ncjuLWQGy+vZW+7cgfI6wPIbfYfP6g7QAutk6W KQx0AoX5woZ6cNxtpIrymaVjSMRRBkKQrJKmRp3pC/lul5E5P2cueMs1fj4OHTbJ lAUv88ywr+R+mRgYQlFW/XQ653f6DT4t6+njfO9oBcPrQDASZel3LjXLpjjYG/N5 +BWnVexuJX9ika8HJiFl55oqaKb+WknfNhk5cPY+x7SDV9ywQeMiDZpr0ffeYAEP LlwwiWRDYpO+uwXHSFF3+JjWwjhs8m8g99iFb7U93yKgBB12dCEPPa2ZeH9wUHMJ sreYhNuq6f4iWWSXpzN45inQqtTi8jrJhuNLTT543ErW7DtntBO2rWMhff3aiXbn Uy3qzZM1nPbuCGuBmP9L2dJ3Z5ifDWB4JmOyWY4swTZGt9AVmUxMIKdZpRONx8vz I9u9nbVPGZBcou50Pa0qTLbkWsSL94MNXrARBxzhHC9Zs6XNEtwN7mOuii7uMkVc adrxgknBH1J1N+NX/eTKzUwJuPvDtA+Z5ILWNN9wpZT/7ed8zEnKHPNUexyeT5g3 uw9z9jH7ffGxFYlx87oiVPHGOrCXYZYW5uoZE31SCBkbtNuffNRJRKIFeipmpJ3P 7bpAG+kGHMelQH6b+5K1Qgsv4tpuSyKeTKpPFH9Av5nN4P1ZBm9N80tzbNWqjSJm S7rYdHnuNEVnUGnRmEUMmVuYZnNBEVN/fP2m2SEwXcP3Uh7TiYlcWw10ygaGmOr7 MvMLGkYgQ4Utwnd98mtqa0jr0hK2TcOSFir3AqVvXN3XJj4cVULkrXe4Im1laWgp -----END RSA PRIVATE KEY----- `), }, { Name: "rsa-encrypted-aes256-ctr", EncryptionKey: "Password-Test_1234", PEMBytes: []byte(`-----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABBb5bVJRJ OyqhVNPrtqeQR7AAAAEAAAAAEAAAEXAAAAB3NzaC1yc2EAAAADAQABAAABAQDGl3Dz88NH mWH65wsj94ffJHmXf09tYTIbJKwSADexk8ETQMBG0CjgM5B5Xu9J9JzTG3QkXFkery9tWb uPCh4y2hz1svJe3gg37YV3Pp98Fmc4rTAKozlJLUdZA8kmURXmGhUe8p6IBT/EF8/ggGqC vphqBwEzQz8jphAzkDBNFfQCpqEblnwG2Kw/Tegso1BPI9EsN3o5wrURCLN8fjPxxEcmWR dGcCDuUqAbwnaJRGup0iJPal7YIVSi3uB7VCK8iWeDtsGLpOAnLojJRzGccn6pnFb1Pjes 35VWkhyv1KH5tDjmq0fA+P40ffSV0T1OZ7J0ZyR/BC0yV0gypHCPAAAD0LaQ/bL0QaRXRv qHn1A+xJIWtkHskTX0APDZ+yyUHye9oaR5LBkvv6x8wPH3j8jb/k6eZ1zfv3g3Xkm5qUOW dI0Lu1KPrwxh+f3Mco7LCDqHsI+j72uTo3sNJjsxhrRuX31rmKUwP4MWNecwVT9uN6Y+lr BjFKfZX3RTEfYzgTh2skPVco90x0Zfbj/Q8IWaPpZFdLzogB+kERZrA7HT8SIGmzLOKDCX KjVP92RkxvleC5T7XcTsXVkVelnfcMQ7sK6tKu6gk3+TUm0FUpOPoPxM1oRfVdyV2Fc3iI tq17lkexQ3PqAjlSkM9VSz8f0ZEKqkQ2oK7m+TPoasMSkn/Blxs6ZnNiToH9Y9xawva/M5 JW822Zbhu8vVGBtpB9DVaHAj3JPAc+l2EwSlHPbyWoNfmQcHilonp+mHru2B/fH+vXOYO5 +T1Fp0gfQMifrDISgcNbTmhKYhnWnnpV6a3JGIe+cUWGzeYn9/GGZAJOHyAyA7Uy70e/2M 9ps4AY2rsS4Fu21BHuskbs9X1ujszIk0prXEN5NAyvKDsTT9chQRjo/VbwWpeAN9adF1Gs yHnOkVT4Vc1reCLi0hNGcqUVH+LAQwTcaEnNaY1mAdgSBY+/8CnSDkhffixzT5ob5HbfLG M2Hax9/pDX9hlLoh+eHz5pLoKzW+YDo1/NypS7goAsHPkiMTpn+oM1bqtjxDLeNmh3gCdW UlJYt8rUT4G0t8JFIhTlVUNULaRYVIypGPgKdkVqUYo3lRW959cxgAC92XepjBISRACQCT jYBWu2RHzbgePTwQLui0udXXFrhL7Tjw+epudZV3CDenBpkaDIGHOb3vOHN/q/lhgyuaTE KN02DGQ1ThBdpAbUJksMj3S8X4aYtZK5Q71o7szjuamf+UseausaLs47BaNKqLugfDNFFl oK2A5VCwJrt+o1wdet86JSnwr5lcJlSF4X9N8Ju6W0qJ/RWoE6znRpuCsVq90i0cgFaVIA OE8CeHdjpXHijVqW/cFbH2IDyUYzSdr5Ke1fow0M61n22D9lm16mXBs5OmkZ5yrb7vJCB7 U2VYIrZIsGQh2VMC/HOt3Ms3HE08jI97MDX/4LlmQEzZwDLYArK7JVVpTCvjdTiY2XrSd6 ZRE5Mw1J5QYHHdw18Ia+tvKVlcQL+RwbTDYzBQrkmHIJ7xYAO5QAtRMZnwSGRyq+f8uhEU nGz9lcHnccxsF6BxuT0XHe1gcUnU71Uffm+D9NLf9xGxEgWkoJK4+/K5DxRYXxYj2iWSMg xQGBlJ2l92yvGabNDbfgqgsvVsTWg= -----END OPENSSH PRIVATE KEY----- `), }, { Name: "dsa-encrypted-aes256-cbc", EncryptionKey: "qG0pher-dsa_t3st$", PEMBytes: []byte(`-----BEGIN DSA PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: AES-128-CBC,7CE7A6E4A647DC01AF860210B15ADE3E hvnBpI99Hceq/55pYRdOzBLntIEis02JFNXuLEydWL+RJBFDn7tA+vXec0ERJd6J G8JXlSOAhmC2H4uK3q2xR8/Y3yL95n6OIcjvCBiLsV+o3jj1MYJmErxP6zRtq4w3 JjIjGHWmaYFSxPKQ6e8fs74HEqaeMV9ONUoTtB+aISmgaBL15Fcoayg245dkBvVl h5Kqspe7yvOBmzA3zjRuxmSCqKJmasXM7mqs3vIrMxZE3XPo1/fWKcPuExgpVQoT HkJZEoIEIIPnPMwT2uYbFJSGgPJVMDT84xz7yvjCdhLmqrsXgs5Qw7Pw0i0c0BUJ b7fDJ2UhdiwSckWGmIhTLlJZzr8K+JpjCDlP+REYBI5meB7kosBnlvCEHdw2EJkH 0QDc/2F4xlVrHOLbPRFyu1Oi2Gvbeoo9EsM/DThpd1hKAlb0sF5Y0y0d+owv0PnE R/4X3HWfIdOHsDUvJ8xVWZ4BZk9Zk9qol045DcFCehpr/3hslCrKSZHakLt9GI58 vVQJ4L0aYp5nloLfzhViZtKJXRLkySMKdzYkIlNmW1oVGl7tce5UCNI8Nok4j6yn IiHM7GBn+0nJoKTXsOGMIBe3ulKlKVxLjEuk9yivh/8= -----END DSA PRIVATE KEY----- `), }, { Name: "rsa-openssh-encrypted-aes256-cbc", EncryptionKey: "12345", PEMBytes: []byte(`-----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jYmMAAAAGYmNyeXB0AAAAGAAAABAr17kxS0 ml6jFNMeKz3kmAAAAAEAAAAAEAAACXAAAAB3NzaC1yc2EAAAADAQABAAAAgQDWgUqHjwHx hEg9L0aUk4z8H/3TOY2/AzWFoXa/50JjZkHpmC5Nuc0dW5tZEavJ4hp556HQzNJpFmnvJt 4xH4hFtOYjxdVkUJFxthj4gX65VchAC4qHkXSIFo9eR2wf+nI6MhnTe2M9K2v1qdhr/tDB QgBDl8EhcXdho7f9wUSt5QAAAhCfga24LXLNKwXb21L7Xl5Qd8MPkNNp9cR7/1E39SEx8Q //hL1DSyQ+kAfuhBkh4oO15ukE8IMxBpsAYkv69PtjIWqFAHKe/AYZe8eQCK3csiMHu8np TCwq6rCaTXl3TkwqnrR908mG9D00fMjsD6FE6ycsofvtxv05VVNdruGm/81dC2FYkW8uU8 k/z+fTj0a9KqxLyxUAX7+Zronnx5m5Z/FPBYfc2boSvTLaKsewcgTY7O7Ut6bvnGR4VRlQ P1ONKhaRdc8jjBHKZhfKHqLQBYMe5YT0QJXwG2wr+VknkzPTTeKPNJvKeiboCB/apBZEB9 Lu+OwZjizwYpXYZ7euT0KMryXXOvdNBHLA5kpjZiYpp7gVHPP1A39SeA2YFdhJ32NRZaXb Gkhhx8egwoNCGhsmnHnc9J0z1J8WIB1dohm7p7nWYEbjd22hwl+huzLSJ1Nwwae51xGCry 9QUplm6ot3EYJIkU8Xh40mR74rL2nMDa70xFYZKwN4F9AoZrcWATMX9L+ARw3m8s3DW4xp Z+R8/30TIeqFzzuVRfLeWyc1AKT0dbwFyz5l8sRlWhUM+jgWx2EU/s+gP8Ch4+3Pjqnu97 1e+V+l6fR0o1r6TmDuOlMe3egsroRTKx+Re4UZGrbWINrFU3KOJmjXf8vOj+IcG23+MMgN uxJ4W16r7KrATcy4l5F45EyinbHR020= -----END OPENSSH PRIVATE KEY----- `), }, { Name: "ed25519-openssh-encrypted-aes256-cbc", EncryptionKey: "quohwan1Ae", PEMBytes: []byte(`-----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jYmMAAAAGYmNyeXB0AAAAGAAAABBkYM/1no eyaYdoda4+kT2wAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAID+XSBBldjetRI5l PsElCyrgFXmGikfg9chCGrY6MW2fAAAAkF2mStmLKKdTDi67NQeZiWv1FaCd9NyKyUYY9s KkgIWxvVVzi8SxJmR2znR4PU26HpX3QUDdOf6ET3BATO9VVWkbBUUqt0VnwB1CI498OYuQ uFv91uY8u4KfYYFN7qZw4sjjPa5m+u1cIGwqVDoNDXR7OddStXwDCcn/9Wq+t2/LoavtsC MgjCX2e9GImZ7kbA== -----END OPENSSH PRIVATE KEY----- `), }, { Name: "ed25519-openssh-encrypted-aes256-ctr", EncryptionKey: "1234", PEMBytes: []byte(`-----BEGIN OPENSSH PRIVATE KEY----- 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 s4LetqIGnhtXJnHsb2en0cZaM= -----END OPENSSH PRIVATE KEY----- `), }, }