@@ -13,11 +13,13 @@ import (
1313 "crypto/ecdsa"
1414 "crypto/elliptic"
1515 "crypto/md5"
16+ "crypto/rand"
1617 "crypto/rsa"
1718 "crypto/sha256"
1819 "crypto/x509"
1920 "encoding/asn1"
2021 "encoding/base64"
22+ "encoding/binary"
2123 "encoding/hex"
2224 "encoding/pem"
2325 "errors"
@@ -295,6 +297,18 @@ func MarshalAuthorizedKey(key PublicKey) []byte {
295297 return b .Bytes ()
296298}
297299
300+ // MarshalPrivateKey returns a PEM block with the private key serialized in the
301+ // OpenSSH format.
302+ func MarshalPrivateKey (key crypto.PrivateKey , comment string ) (* pem.Block , error ) {
303+ return marshalOpenSSHPrivateKey (key , comment , unencryptedOpenSSHMarshaler )
304+ }
305+
306+ // MarshalPrivateKeyWithPassphrase returns a PEM block holding the encrypted
307+ // private key serialized in the OpenSSH format.
308+ func MarshalPrivateKeyWithPassphrase (key crypto.PrivateKey , comment string , passphrase []byte ) (* pem.Block , error ) {
309+ return marshalOpenSSHPrivateKey (key , comment , passphraseProtectedOpenSSHMarshaler (passphrase ))
310+ }
311+
298312// PublicKey represents a public key using an unspecified algorithm.
299313//
300314// Some PublicKeys provided by this package also implement CryptoPublicKey.
@@ -1241,28 +1255,106 @@ func passphraseProtectedOpenSSHKey(passphrase []byte) openSSHDecryptFunc {
12411255 }
12421256}
12431257
1258+ func unencryptedOpenSSHMarshaler (privKeyBlock []byte ) ([]byte , string , string , string , error ) {
1259+ key := generateOpenSSHPadding (privKeyBlock , 8 )
1260+ return key , "none" , "none" , "" , nil
1261+ }
1262+
1263+ func passphraseProtectedOpenSSHMarshaler (passphrase []byte ) openSSHEncryptFunc {
1264+ return func (privKeyBlock []byte ) ([]byte , string , string , string , error ) {
1265+ salt := make ([]byte , 16 )
1266+ if _ , err := rand .Read (salt ); err != nil {
1267+ return nil , "" , "" , "" , err
1268+ }
1269+
1270+ opts := struct {
1271+ Salt []byte
1272+ Rounds uint32
1273+ }{salt , 16 }
1274+
1275+ // Derive key to encrypt the private key block.
1276+ k , err := bcrypt_pbkdf .Key (passphrase , salt , int (opts .Rounds ), 32 + aes .BlockSize )
1277+ if err != nil {
1278+ return nil , "" , "" , "" , err
1279+ }
1280+
1281+ // Add padding matching the block size of AES.
1282+ keyBlock := generateOpenSSHPadding (privKeyBlock , aes .BlockSize )
1283+
1284+ // Encrypt the private key using the derived secret.
1285+
1286+ dst := make ([]byte , len (keyBlock ))
1287+ key , iv := k [:32 ], k [32 :]
1288+ block , err := aes .NewCipher (key )
1289+ if err != nil {
1290+ return nil , "" , "" , "" , err
1291+ }
1292+
1293+ stream := cipher .NewCTR (block , iv )
1294+ stream .XORKeyStream (dst , keyBlock )
1295+
1296+ return dst , "aes256-ctr" , "bcrypt" , string (Marshal (opts )), nil
1297+ }
1298+ }
1299+
1300+ const privateKeyAuthMagic = "openssh-key-v1\x00 "
1301+
12441302type openSSHDecryptFunc func (CipherName , KdfName , KdfOpts string , PrivKeyBlock []byte ) ([]byte , error )
1303+ type openSSHEncryptFunc func (PrivKeyBlock []byte ) (ProtectedKeyBlock []byte , cipherName , kdfName , kdfOptions string , err error )
1304+
1305+ type openSSHEncryptedPrivateKey struct {
1306+ CipherName string
1307+ KdfName string
1308+ KdfOpts string
1309+ NumKeys uint32
1310+ PubKey []byte
1311+ PrivKeyBlock []byte
1312+ }
1313+
1314+ type openSSHPrivateKey struct {
1315+ Check1 uint32
1316+ Check2 uint32
1317+ Keytype string
1318+ Rest []byte `ssh:"rest"`
1319+ }
1320+
1321+ type openSSHRSAPrivateKey struct {
1322+ N * big.Int
1323+ E * big.Int
1324+ D * big.Int
1325+ Iqmp * big.Int
1326+ P * big.Int
1327+ Q * big.Int
1328+ Comment string
1329+ Pad []byte `ssh:"rest"`
1330+ }
1331+
1332+ type openSSHEd25519PrivateKey struct {
1333+ Pub []byte
1334+ Priv []byte
1335+ Comment string
1336+ Pad []byte `ssh:"rest"`
1337+ }
1338+
1339+ type openSSHECDSAPrivateKey struct {
1340+ Curve string
1341+ Pub []byte
1342+ D * big.Int
1343+ Comment string
1344+ Pad []byte `ssh:"rest"`
1345+ }
12451346
12461347// parseOpenSSHPrivateKey parses an OpenSSH private key, using the decrypt
12471348// function to unwrap the encrypted portion. unencryptedOpenSSHKey can be used
12481349// as the decrypt function to parse an unencrypted private key. See
12491350// https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key.
12501351func parseOpenSSHPrivateKey (key []byte , decrypt openSSHDecryptFunc ) (crypto.PrivateKey , error ) {
1251- const magic = "openssh-key-v1\x00 "
1252- if len (key ) < len (magic ) || string (key [:len (magic )]) != magic {
1352+ if len (key ) < len (privateKeyAuthMagic ) || string (key [:len (privateKeyAuthMagic )]) != privateKeyAuthMagic {
12531353 return nil , errors .New ("ssh: invalid openssh private key format" )
12541354 }
1255- remaining := key [len (magic ):]
1256-
1257- var w struct {
1258- CipherName string
1259- KdfName string
1260- KdfOpts string
1261- NumKeys uint32
1262- PubKey []byte
1263- PrivKeyBlock []byte
1264- }
1355+ remaining := key [len (privateKeyAuthMagic ):]
12651356
1357+ var w openSSHEncryptedPrivateKey
12661358 if err := Unmarshal (remaining , & w ); err != nil {
12671359 return nil , err
12681360 }
@@ -1284,13 +1376,7 @@ func parseOpenSSHPrivateKey(key []byte, decrypt openSSHDecryptFunc) (crypto.Priv
12841376 return nil , err
12851377 }
12861378
1287- pk1 := struct {
1288- Check1 uint32
1289- Check2 uint32
1290- Keytype string
1291- Rest []byte `ssh:"rest"`
1292- }{}
1293-
1379+ var pk1 openSSHPrivateKey
12941380 if err := Unmarshal (privKeyBlock , & pk1 ); err != nil || pk1 .Check1 != pk1 .Check2 {
12951381 if w .CipherName != "none" {
12961382 return nil , x509 .IncorrectPasswordError
@@ -1300,18 +1386,7 @@ func parseOpenSSHPrivateKey(key []byte, decrypt openSSHDecryptFunc) (crypto.Priv
13001386
13011387 switch pk1 .Keytype {
13021388 case KeyAlgoRSA :
1303- // https://github.com/openssh/openssh-portable/blob/master/sshkey.c#L2760-L2773
1304- key := struct {
1305- N * big.Int
1306- E * big.Int
1307- D * big.Int
1308- Iqmp * big.Int
1309- P * big.Int
1310- Q * big.Int
1311- Comment string
1312- Pad []byte `ssh:"rest"`
1313- }{}
1314-
1389+ var key openSSHRSAPrivateKey
13151390 if err := Unmarshal (pk1 .Rest , & key ); err != nil {
13161391 return nil , err
13171392 }
@@ -1337,13 +1412,7 @@ func parseOpenSSHPrivateKey(key []byte, decrypt openSSHDecryptFunc) (crypto.Priv
13371412
13381413 return pk , nil
13391414 case KeyAlgoED25519 :
1340- key := struct {
1341- Pub []byte
1342- Priv []byte
1343- Comment string
1344- Pad []byte `ssh:"rest"`
1345- }{}
1346-
1415+ var key openSSHEd25519PrivateKey
13471416 if err := Unmarshal (pk1 .Rest , & key ); err != nil {
13481417 return nil , err
13491418 }
@@ -1360,14 +1429,7 @@ func parseOpenSSHPrivateKey(key []byte, decrypt openSSHDecryptFunc) (crypto.Priv
13601429 copy (pk , key .Priv )
13611430 return & pk , nil
13621431 case KeyAlgoECDSA256 , KeyAlgoECDSA384 , KeyAlgoECDSA521 :
1363- key := struct {
1364- Curve string
1365- Pub []byte
1366- D * big.Int
1367- Comment string
1368- Pad []byte `ssh:"rest"`
1369- }{}
1370-
1432+ var key openSSHECDSAPrivateKey
13711433 if err := Unmarshal (pk1 .Rest , & key ); err != nil {
13721434 return nil , err
13731435 }
@@ -1415,6 +1477,131 @@ func parseOpenSSHPrivateKey(key []byte, decrypt openSSHDecryptFunc) (crypto.Priv
14151477 }
14161478}
14171479
1480+ func marshalOpenSSHPrivateKey (key crypto.PrivateKey , comment string , encrypt openSSHEncryptFunc ) (* pem.Block , error ) {
1481+ var w openSSHEncryptedPrivateKey
1482+ var pk1 openSSHPrivateKey
1483+
1484+ // Random check bytes.
1485+ var check uint32
1486+ if err := binary .Read (rand .Reader , binary .BigEndian , & check ); err != nil {
1487+ return nil , err
1488+ }
1489+
1490+ pk1 .Check1 = check
1491+ pk1 .Check2 = check
1492+ w .NumKeys = 1
1493+
1494+ // Use a []byte directly on ed25519 keys.
1495+ if k , ok := key .(* ed25519.PrivateKey ); ok {
1496+ key = * k
1497+ }
1498+
1499+ switch k := key .(type ) {
1500+ case * rsa.PrivateKey :
1501+ E := new (big.Int ).SetInt64 (int64 (k .PublicKey .E ))
1502+ // Marshal public key:
1503+ // E and N are in reversed order in the public and private key.
1504+ pubKey := struct {
1505+ KeyType string
1506+ E * big.Int
1507+ N * big.Int
1508+ }{
1509+ KeyAlgoRSA ,
1510+ E , k .PublicKey .N ,
1511+ }
1512+ w .PubKey = Marshal (pubKey )
1513+
1514+ // Marshal private key.
1515+ key := openSSHRSAPrivateKey {
1516+ N : k .PublicKey .N ,
1517+ E : E ,
1518+ D : k .D ,
1519+ Iqmp : k .Precomputed .Qinv ,
1520+ P : k .Primes [0 ],
1521+ Q : k .Primes [1 ],
1522+ Comment : comment ,
1523+ }
1524+ pk1 .Keytype = KeyAlgoRSA
1525+ pk1 .Rest = Marshal (key )
1526+ case ed25519.PrivateKey :
1527+ pub := make ([]byte , ed25519 .PublicKeySize )
1528+ priv := make ([]byte , ed25519 .PrivateKeySize )
1529+ copy (pub , k [32 :])
1530+ copy (priv , k )
1531+
1532+ // Marshal public key.
1533+ pubKey := struct {
1534+ KeyType string
1535+ Pub []byte
1536+ }{
1537+ KeyAlgoED25519 , pub ,
1538+ }
1539+ w .PubKey = Marshal (pubKey )
1540+
1541+ // Marshal private key.
1542+ key := openSSHEd25519PrivateKey {
1543+ Pub : pub ,
1544+ Priv : priv ,
1545+ Comment : comment ,
1546+ }
1547+ pk1 .Keytype = KeyAlgoED25519
1548+ pk1 .Rest = Marshal (key )
1549+ case * ecdsa.PrivateKey :
1550+ var curve , keyType string
1551+ switch name := k .Curve .Params ().Name ; name {
1552+ case "P-256" :
1553+ curve = "nistp256"
1554+ keyType = KeyAlgoECDSA256
1555+ case "P-384" :
1556+ curve = "nistp384"
1557+ keyType = KeyAlgoECDSA384
1558+ case "P-521" :
1559+ curve = "nistp521"
1560+ keyType = KeyAlgoECDSA521
1561+ default :
1562+ return nil , errors .New ("ssh: unhandled elliptic curve " + name )
1563+ }
1564+
1565+ pub := elliptic .Marshal (k .Curve , k .PublicKey .X , k .PublicKey .Y )
1566+
1567+ // Marshal public key.
1568+ pubKey := struct {
1569+ KeyType string
1570+ Curve string
1571+ Pub []byte
1572+ }{
1573+ keyType , curve , pub ,
1574+ }
1575+ w .PubKey = Marshal (pubKey )
1576+
1577+ // Marshal private key.
1578+ key := openSSHECDSAPrivateKey {
1579+ Curve : curve ,
1580+ Pub : pub ,
1581+ D : k .D ,
1582+ Comment : comment ,
1583+ }
1584+ pk1 .Keytype = keyType
1585+ pk1 .Rest = Marshal (key )
1586+ default :
1587+ return nil , fmt .Errorf ("ssh: unsupported key type %T" , k )
1588+ }
1589+
1590+ var err error
1591+ // Add padding and encrypt the key if necessary.
1592+ w .PrivKeyBlock , w .CipherName , w .KdfName , w .KdfOpts , err = encrypt (Marshal (pk1 ))
1593+ if err != nil {
1594+ return nil , err
1595+ }
1596+
1597+ b := Marshal (w )
1598+ block := & pem.Block {
1599+ Type : "OPENSSH PRIVATE KEY" ,
1600+ Bytes : append ([]byte (privateKeyAuthMagic ), b ... ),
1601+ }
1602+ return block , nil
1603+ }
1604+
14181605func checkOpenSSHKeyPadding (pad []byte ) error {
14191606 for i , b := range pad {
14201607 if int (b ) != i + 1 {
@@ -1424,6 +1611,13 @@ func checkOpenSSHKeyPadding(pad []byte) error {
14241611 return nil
14251612}
14261613
1614+ func generateOpenSSHPadding (block []byte , blockSize int ) []byte {
1615+ for i , l := 0 , len (block ); (l + i )% blockSize != 0 ; i ++ {
1616+ block = append (block , byte (i + 1 ))
1617+ }
1618+ return block
1619+ }
1620+
14271621// FingerprintLegacyMD5 returns the user presentation of the key's
14281622// fingerprint as described by RFC 4716 section 4.
14291623func FingerprintLegacyMD5 (pubKey PublicKey ) string {
0 commit comments