ssh.go (9822B)
1 // Copyright 2019 Google LLC 2 // 3 // Use of this source code is governed by a BSD-style 4 // license that can be found in the LICENSE file or at 5 // https://developers.google.com/open-source/licenses/bsd 6 7 package age 8 9 import ( 10 "bytes" 11 "crypto/ed25519" 12 "crypto/rand" 13 "crypto/rsa" 14 "crypto/sha256" 15 "crypto/sha512" 16 "errors" 17 "fmt" 18 "io" 19 "math/big" 20 21 "github.com/FiloSottile/age/internal/format" 22 "golang.org/x/crypto/chacha20poly1305" 23 "golang.org/x/crypto/curve25519" 24 "golang.org/x/crypto/hkdf" 25 "golang.org/x/crypto/ssh" 26 ) 27 28 const oaepLabel = "age-tool.com ssh-rsa" 29 30 type SSHRSARecipient struct { 31 sshKey ssh.PublicKey 32 pubKey *rsa.PublicKey 33 } 34 35 var _ Recipient = &SSHRSARecipient{} 36 37 func (*SSHRSARecipient) Type() string { return "ssh-rsa" } 38 39 func NewSSHRSARecipient(pk ssh.PublicKey) (*SSHRSARecipient, error) { 40 if pk.Type() != "ssh-rsa" { 41 return nil, errors.New("SSH public key is not an RSA key") 42 } 43 r := &SSHRSARecipient{ 44 sshKey: pk, 45 } 46 47 if pk, ok := pk.(ssh.CryptoPublicKey); ok { 48 if pk, ok := pk.CryptoPublicKey().(*rsa.PublicKey); ok { 49 r.pubKey = pk 50 } else { 51 return nil, errors.New("unexpected public key type") 52 } 53 } else { 54 return nil, errors.New("pk does not implement ssh.CryptoPublicKey") 55 } 56 return r, nil 57 } 58 59 func (r *SSHRSARecipient) Wrap(fileKey []byte) (*format.Recipient, error) { 60 h := sha256.New() 61 h.Write(r.sshKey.Marshal()) 62 hh := h.Sum(nil) 63 64 l := &format.Recipient{ 65 Type: "ssh-rsa", 66 Args: []string{format.EncodeToString(hh[:4])}, 67 } 68 69 wrappedKey, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, 70 r.pubKey, fileKey, []byte(oaepLabel)) 71 if err != nil { 72 return nil, err 73 } 74 l.Body = []byte(format.EncodeToString(wrappedKey) + "\n") 75 76 return l, nil 77 } 78 79 type SSHRSAIdentity struct { 80 k *rsa.PrivateKey 81 sshKey ssh.PublicKey 82 } 83 84 var _ Identity = &SSHRSAIdentity{} 85 86 func (*SSHRSAIdentity) Type() string { return "ssh-rsa" } 87 88 func NewSSHRSAIdentity(key *rsa.PrivateKey) (*SSHRSAIdentity, error) { 89 s, err := ssh.NewSignerFromKey(key) 90 if err != nil { 91 return nil, err 92 } 93 i := &SSHRSAIdentity{ 94 k: key, sshKey: s.PublicKey(), 95 } 96 return i, nil 97 } 98 99 func (i *SSHRSAIdentity) Unwrap(block *format.Recipient) ([]byte, error) { 100 if block.Type != "ssh-rsa" { 101 return nil, errors.New("wrong recipient block type") 102 } 103 if len(block.Args) != 1 { 104 return nil, errors.New("invalid ssh-rsa recipient block") 105 } 106 hash, err := format.DecodeString(block.Args[0]) 107 if err != nil { 108 return nil, fmt.Errorf("failed to parse ssh-rsa recipient: %v", err) 109 } 110 if len(hash) != 4 { 111 return nil, errors.New("invalid ssh-rsa recipient block") 112 } 113 wrappedKey, err := format.DecodeString(string(block.Body)) 114 if err != nil { 115 return nil, fmt.Errorf("failed to parse ssh-rsa recipient: %v", err) 116 } 117 118 h := sha256.New() 119 h.Write(i.sshKey.Marshal()) 120 hh := h.Sum(nil) 121 if !bytes.Equal(hh[:4], hash) { 122 return nil, errors.New("wrong ssh-rsa key") 123 } 124 125 fileKey, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, i.k, 126 wrappedKey, []byte(oaepLabel)) 127 if err != nil { 128 return nil, fmt.Errorf("failed to decrypt file key: %v", err) 129 } 130 return fileKey, nil 131 } 132 133 type SSHEd25519Recipient struct { 134 sshKey ssh.PublicKey 135 theirPublicKey [32]byte 136 } 137 138 var _ Recipient = &SSHEd25519Recipient{} 139 140 func (*SSHEd25519Recipient) Type() string { return "ssh-ed25519" } 141 142 func NewSSHEd25519Recipient(pk ssh.PublicKey) (*SSHEd25519Recipient, error) { 143 if pk.Type() != "ssh-ed25519" { 144 return nil, errors.New("SSH public key is not an Ed25519 key") 145 } 146 r := &SSHEd25519Recipient{ 147 sshKey: pk, 148 } 149 150 if pk, ok := pk.(ssh.CryptoPublicKey); ok { 151 if pk, ok := pk.CryptoPublicKey().(ed25519.PublicKey); ok { 152 pubKey := ed25519PublicKeyToCurve25519(pk) 153 copy(r.theirPublicKey[:], pubKey) 154 } else { 155 return nil, errors.New("unexpected public key type") 156 } 157 } else { 158 return nil, errors.New("pk does not implement ssh.CryptoPublicKey") 159 } 160 return r, nil 161 } 162 163 func ParseSSHRecipient(s string) (Recipient, error) { 164 pubKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(s)) 165 if err != nil { 166 return nil, fmt.Errorf("malformed SSH recipient: %q: %v", s, err) 167 } 168 169 var r Recipient 170 switch t := pubKey.Type(); t { 171 case "ssh-rsa": 172 r, err = NewSSHRSARecipient(pubKey) 173 case "ssh-ed25519": 174 r, err = NewSSHEd25519Recipient(pubKey) 175 default: 176 return nil, fmt.Errorf("unknown SSH recipient type: %q", t) 177 } 178 if err != nil { 179 return nil, fmt.Errorf("malformed SSH recipient: %q: %v", s, err) 180 } 181 182 return r, nil 183 } 184 185 var curve25519P, _ = new(big.Int).SetString("57896044618658097711785492504343953926634992332820282019728792003956564819949", 10) 186 187 func ed25519PublicKeyToCurve25519(pk ed25519.PublicKey) []byte { 188 // ed25519.PublicKey is a little endian representation of the y-coordinate, 189 // with the most significant bit set based on the sign of the x-ccordinate. 190 bigEndianY := make([]byte, ed25519.PublicKeySize) 191 for i, b := range pk { 192 bigEndianY[ed25519.PublicKeySize-i-1] = b 193 } 194 bigEndianY[0] &= 0b0111_1111 195 196 // The Montgomery u-coordinate is derived through the bilinear map 197 // 198 // u = (1 + y) / (1 - y) 199 // 200 // See https://blog.filippo.io/using-ed25519-keys-for-encryption. 201 y := new(big.Int).SetBytes(bigEndianY) 202 denom := big.NewInt(1) 203 denom.ModInverse(denom.Sub(denom, y), curve25519P) // 1 / (1 - y) 204 u := y.Mul(y.Add(y, big.NewInt(1)), denom) 205 u.Mod(u, curve25519P) 206 207 out := make([]byte, 32) 208 uBytes := u.Bytes() 209 for i, b := range uBytes { 210 out[len(uBytes)-i-1] = b 211 } 212 213 return out 214 } 215 216 const ed25519Label = "age-tool.com ssh-ed25519" 217 218 func (r *SSHEd25519Recipient) Wrap(fileKey []byte) (*format.Recipient, error) { 219 // TODO: DRY this up with the X25519 implementation. 220 var ephemeral, ourPublicKey [32]byte 221 if _, err := rand.Read(ephemeral[:]); err != nil { 222 return nil, err 223 } 224 curve25519.ScalarBaseMult(&ourPublicKey, &ephemeral) 225 226 var sharedSecret, tweak [32]byte 227 tH := hkdf.New(sha256.New, nil, r.sshKey.Marshal(), []byte(ed25519Label)) 228 if _, err := io.ReadFull(tH, tweak[:]); err != nil { 229 return nil, err 230 } 231 curve25519.ScalarMult(&sharedSecret, &ephemeral, &r.theirPublicKey) 232 curve25519.ScalarMult(&sharedSecret, &tweak, &sharedSecret) 233 234 sH := sha256.New() 235 sH.Write(r.sshKey.Marshal()) 236 hh := sH.Sum(nil) 237 238 l := &format.Recipient{ 239 Type: "ssh-ed25519", 240 Args: []string{format.EncodeToString(hh[:4]), 241 format.EncodeToString(ourPublicKey[:])}, 242 } 243 244 salt := make([]byte, 0, 32*2) 245 salt = append(salt, ourPublicKey[:]...) 246 salt = append(salt, r.theirPublicKey[:]...) 247 h := hkdf.New(sha256.New, sharedSecret[:], salt, []byte(ed25519Label)) 248 wrappingKey := make([]byte, chacha20poly1305.KeySize) 249 if _, err := io.ReadFull(h, wrappingKey); err != nil { 250 return nil, err 251 } 252 253 wrappedKey, err := aeadEncrypt(wrappingKey, fileKey) 254 if err != nil { 255 return nil, err 256 } 257 l.Body = []byte(format.EncodeToString(wrappedKey) + "\n") 258 259 return l, nil 260 } 261 262 type SSHEd25519Identity struct { 263 secretKey, ourPublicKey [32]byte 264 sshKey ssh.PublicKey 265 } 266 267 var _ Identity = &SSHEd25519Identity{} 268 269 func (*SSHEd25519Identity) Type() string { return "ssh-ed25519" } 270 271 func NewSSHEd25519Identity(key ed25519.PrivateKey) (*SSHEd25519Identity, error) { 272 s, err := ssh.NewSignerFromKey(key) 273 if err != nil { 274 return nil, err 275 } 276 i := &SSHEd25519Identity{ 277 sshKey: s.PublicKey(), 278 } 279 secretKey := ed25519PrivateKeyToCurve25519(key) 280 copy(i.secretKey[:], secretKey) 281 curve25519.ScalarBaseMult(&i.ourPublicKey, &i.secretKey) 282 return i, nil 283 } 284 285 func ParseSSHIdentity(pemBytes []byte) (Identity, error) { 286 k, err := ssh.ParseRawPrivateKey(pemBytes) 287 if err != nil { 288 return nil, err 289 } 290 291 switch k := k.(type) { 292 case *ed25519.PrivateKey: 293 return NewSSHEd25519Identity(*k) 294 case *rsa.PrivateKey: 295 return NewSSHRSAIdentity(k) 296 } 297 298 return nil, fmt.Errorf("unsupported SSH identity type: %T", k) 299 } 300 301 func ed25519PrivateKeyToCurve25519(pk ed25519.PrivateKey) []byte { 302 h := sha512.New() 303 h.Write(pk[:32]) 304 out := h.Sum(nil) 305 return out[:32] 306 } 307 308 func (i *SSHEd25519Identity) Unwrap(block *format.Recipient) ([]byte, error) { 309 // TODO: DRY this up with the X25519 implementation. 310 if block.Type != "ssh-ed25519" { 311 return nil, errors.New("wrong recipient block type") 312 } 313 if len(block.Args) != 2 { 314 return nil, errors.New("invalid ssh-ed25519 recipient block") 315 } 316 hash, err := format.DecodeString(block.Args[0]) 317 if err != nil { 318 return nil, fmt.Errorf("failed to parse ssh-ed25519 recipient: %v", err) 319 } 320 if len(hash) != 4 { 321 return nil, errors.New("invalid ssh-ed25519 recipient block") 322 } 323 publicKey, err := format.DecodeString(block.Args[1]) 324 if err != nil { 325 return nil, fmt.Errorf("failed to parse ssh-ed25519 recipient: %v", err) 326 } 327 if len(publicKey) != 32 { 328 return nil, errors.New("invalid ssh-ed25519 recipient block") 329 } 330 wrappedKey, err := format.DecodeString(string(block.Body)) 331 if err != nil { 332 return nil, fmt.Errorf("failed to parse ssh-ed25519 recipient: %v", err) 333 } 334 335 sH := sha256.New() 336 sH.Write(i.sshKey.Marshal()) 337 hh := sH.Sum(nil) 338 if !bytes.Equal(hh[:4], hash) { 339 return nil, errors.New("wrong ssh-ed25519 key") 340 } 341 342 var sharedSecret, theirPublicKey, tweak [32]byte 343 copy(theirPublicKey[:], publicKey) 344 tH := hkdf.New(sha256.New, nil, i.sshKey.Marshal(), []byte(ed25519Label)) 345 if _, err := io.ReadFull(tH, tweak[:]); err != nil { 346 return nil, err 347 } 348 curve25519.ScalarMult(&sharedSecret, &i.secretKey, &theirPublicKey) 349 curve25519.ScalarMult(&sharedSecret, &tweak, &sharedSecret) 350 351 salt := make([]byte, 0, 32*2) 352 salt = append(salt, theirPublicKey[:]...) 353 salt = append(salt, i.ourPublicKey[:]...) 354 h := hkdf.New(sha256.New, sharedSecret[:], salt, []byte(ed25519Label)) 355 wrappingKey := make([]byte, chacha20poly1305.KeySize) 356 if _, err := io.ReadFull(h, wrappingKey); err != nil { 357 return nil, err 358 } 359 360 fileKey, err := aeadDecrypt(wrappingKey, wrappedKey) 361 if err != nil { 362 return nil, fmt.Errorf("failed to decrypt file key: %v", err) 363 } 364 return fileKey, nil 365 }