@@ -13,11 +13,13 @@ import (
13
13
"crypto/ecdsa"
14
14
"crypto/elliptic"
15
15
"crypto/md5"
16
+ "crypto/rand"
16
17
"crypto/rsa"
17
18
"crypto/sha256"
18
19
"crypto/x509"
19
20
"encoding/asn1"
20
21
"encoding/base64"
22
+ "encoding/binary"
21
23
"encoding/hex"
22
24
"encoding/pem"
23
25
"errors"
@@ -295,6 +297,18 @@ func MarshalAuthorizedKey(key PublicKey) []byte {
295
297
return b .Bytes ()
296
298
}
297
299
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
+
298
312
// PublicKey represents a public key using an unspecified algorithm.
299
313
//
300
314
// Some PublicKeys provided by this package also implement CryptoPublicKey.
@@ -1241,28 +1255,106 @@ func passphraseProtectedOpenSSHKey(passphrase []byte) openSSHDecryptFunc {
1241
1255
}
1242
1256
}
1243
1257
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
+
1244
1302
type 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
+ }
1245
1346
1246
1347
// parseOpenSSHPrivateKey parses an OpenSSH private key, using the decrypt
1247
1348
// function to unwrap the encrypted portion. unencryptedOpenSSHKey can be used
1248
1349
// as the decrypt function to parse an unencrypted private key. See
1249
1350
// https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key.
1250
1351
func 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 {
1253
1353
return nil , errors .New ("ssh: invalid openssh private key format" )
1254
1354
}
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 ):]
1265
1356
1357
+ var w openSSHEncryptedPrivateKey
1266
1358
if err := Unmarshal (remaining , & w ); err != nil {
1267
1359
return nil , err
1268
1360
}
@@ -1284,13 +1376,7 @@ func parseOpenSSHPrivateKey(key []byte, decrypt openSSHDecryptFunc) (crypto.Priv
1284
1376
return nil , err
1285
1377
}
1286
1378
1287
- pk1 := struct {
1288
- Check1 uint32
1289
- Check2 uint32
1290
- Keytype string
1291
- Rest []byte `ssh:"rest"`
1292
- }{}
1293
-
1379
+ var pk1 openSSHPrivateKey
1294
1380
if err := Unmarshal (privKeyBlock , & pk1 ); err != nil || pk1 .Check1 != pk1 .Check2 {
1295
1381
if w .CipherName != "none" {
1296
1382
return nil , x509 .IncorrectPasswordError
@@ -1300,18 +1386,7 @@ func parseOpenSSHPrivateKey(key []byte, decrypt openSSHDecryptFunc) (crypto.Priv
1300
1386
1301
1387
switch pk1 .Keytype {
1302
1388
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
1315
1390
if err := Unmarshal (pk1 .Rest , & key ); err != nil {
1316
1391
return nil , err
1317
1392
}
@@ -1337,13 +1412,7 @@ func parseOpenSSHPrivateKey(key []byte, decrypt openSSHDecryptFunc) (crypto.Priv
1337
1412
1338
1413
return pk , nil
1339
1414
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
1347
1416
if err := Unmarshal (pk1 .Rest , & key ); err != nil {
1348
1417
return nil , err
1349
1418
}
@@ -1360,14 +1429,7 @@ func parseOpenSSHPrivateKey(key []byte, decrypt openSSHDecryptFunc) (crypto.Priv
1360
1429
copy (pk , key .Priv )
1361
1430
return & pk , nil
1362
1431
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
1371
1433
if err := Unmarshal (pk1 .Rest , & key ); err != nil {
1372
1434
return nil , err
1373
1435
}
@@ -1415,6 +1477,131 @@ func parseOpenSSHPrivateKey(key []byte, decrypt openSSHDecryptFunc) (crypto.Priv
1415
1477
}
1416
1478
}
1417
1479
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
+
1418
1605
func checkOpenSSHKeyPadding (pad []byte ) error {
1419
1606
for i , b := range pad {
1420
1607
if int (b ) != i + 1 {
@@ -1424,6 +1611,13 @@ func checkOpenSSHKeyPadding(pad []byte) error {
1424
1611
return nil
1425
1612
}
1426
1613
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
+
1427
1621
// FingerprintLegacyMD5 returns the user presentation of the key's
1428
1622
// fingerprint as described by RFC 4716 section 4.
1429
1623
func FingerprintLegacyMD5 (pubKey PublicKey ) string {
0 commit comments