github.com/susy-go/susy-graviton@v0.0.0-20190614130430-36cddae42305/swarm/api/act.go (about) 1 package api 2 3 import ( 4 "context" 5 "crypto/ecdsa" 6 "crypto/rand" 7 "encoding/hex" 8 "encoding/json" 9 "errors" 10 "fmt" 11 "io" 12 "strings" 13 "time" 14 15 "github.com/susy-go/susy-graviton/common" 16 "github.com/susy-go/susy-graviton/crypto" 17 "github.com/susy-go/susy-graviton/crypto/ecies" 18 "github.com/susy-go/susy-graviton/swarm/log" 19 "github.com/susy-go/susy-graviton/swarm/sctx" 20 "github.com/susy-go/susy-graviton/swarm/storage" 21 "golang.org/x/crypto/scrypt" 22 "golang.org/x/crypto/sha3" 23 cli "gopkg.in/urfave/cli.v1" 24 ) 25 26 var ( 27 ErrDecrypt = errors.New("cant decrypt - forbidden") 28 ErrUnknownAccessType = errors.New("unknown access type (or not implemented)") 29 ErrDecryptDomainForbidden = errors.New("decryption request domain forbidden - can only decrypt on localhost") 30 AllowedDecryptDomains = []string{ 31 "localhost", 32 "127.0.0.1", 33 } 34 ) 35 36 const EMPTY_CREDENTIALS = "" 37 38 type AccessEntry struct { 39 Type AccessType 40 Publisher string 41 Salt []byte 42 Act string 43 KdfParams *KdfParams 44 } 45 46 type DecryptFunc func(*ManifestEntry) error 47 48 func (a *AccessEntry) MarshalJSON() (out []byte, err error) { 49 50 return json.Marshal(struct { 51 Type AccessType `json:"type,omitempty"` 52 Publisher string `json:"publisher,omitempty"` 53 Salt string `json:"salt,omitempty"` 54 Act string `json:"act,omitempty"` 55 KdfParams *KdfParams `json:"kdf_params,omitempty"` 56 }{ 57 Type: a.Type, 58 Publisher: a.Publisher, 59 Salt: hex.EncodeToString(a.Salt), 60 Act: a.Act, 61 KdfParams: a.KdfParams, 62 }) 63 64 } 65 66 func (a *AccessEntry) UnmarshalJSON(value []byte) error { 67 v := struct { 68 Type AccessType `json:"type,omitempty"` 69 Publisher string `json:"publisher,omitempty"` 70 Salt string `json:"salt,omitempty"` 71 Act string `json:"act,omitempty"` 72 KdfParams *KdfParams `json:"kdf_params,omitempty"` 73 }{} 74 75 err := json.Unmarshal(value, &v) 76 if err != nil { 77 return err 78 } 79 a.Act = v.Act 80 a.KdfParams = v.KdfParams 81 a.Publisher = v.Publisher 82 a.Salt, err = hex.DecodeString(v.Salt) 83 if err != nil { 84 return err 85 } 86 if len(a.Salt) != 32 { 87 return errors.New("salt should be 32 bytes long") 88 } 89 a.Type = v.Type 90 return nil 91 } 92 93 type KdfParams struct { 94 N int `json:"n"` 95 P int `json:"p"` 96 R int `json:"r"` 97 } 98 99 type AccessType string 100 101 const AccessTypePass = AccessType("pass") 102 const AccessTypePK = AccessType("pk") 103 const AccessTypeACT = AccessType("act") 104 105 // NewAccessEntryPassword creates a manifest AccessEntry in order to create an ACT protected by a password 106 func NewAccessEntryPassword(salt []byte, kdfParams *KdfParams) (*AccessEntry, error) { 107 if len(salt) != 32 { 108 return nil, fmt.Errorf("salt should be 32 bytes long") 109 } 110 return &AccessEntry{ 111 Type: AccessTypePass, 112 Salt: salt, 113 KdfParams: kdfParams, 114 }, nil 115 } 116 117 // NewAccessEntryPK creates a manifest AccessEntry in order to create an ACT protected by a pair of Elliptic Curve keys 118 func NewAccessEntryPK(publisher string, salt []byte) (*AccessEntry, error) { 119 if len(publisher) != 66 { 120 return nil, fmt.Errorf("publisher should be 66 characters long, got %d", len(publisher)) 121 } 122 if len(salt) != 32 { 123 return nil, fmt.Errorf("salt should be 32 bytes long") 124 } 125 return &AccessEntry{ 126 Type: AccessTypePK, 127 Publisher: publisher, 128 Salt: salt, 129 }, nil 130 } 131 132 // NewAccessEntryACT creates a manifest AccessEntry in order to create an ACT protected by a combination of EC keys and passwords 133 func NewAccessEntryACT(publisher string, salt []byte, act string) (*AccessEntry, error) { 134 if len(salt) != 32 { 135 return nil, fmt.Errorf("salt should be 32 bytes long") 136 } 137 if len(publisher) != 66 { 138 return nil, fmt.Errorf("publisher should be 66 characters long") 139 } 140 141 return &AccessEntry{ 142 Type: AccessTypeACT, 143 Publisher: publisher, 144 Salt: salt, 145 Act: act, 146 KdfParams: DefaultKdfParams, 147 }, nil 148 } 149 150 // NOOPDecrypt is a generic decrypt function that is passed into the API in places where real ACT decryption capabilities are 151 // either unwanted, or alternatively, cannot be implemented in the immediate scope 152 func NOOPDecrypt(*ManifestEntry) error { 153 return nil 154 } 155 156 var DefaultKdfParams = NewKdfParams(262144, 1, 8) 157 158 // NewKdfParams returns a KdfParams struct with the given scrypt params 159 func NewKdfParams(n, p, r int) *KdfParams { 160 161 return &KdfParams{ 162 N: n, 163 P: p, 164 R: r, 165 } 166 } 167 168 // NewSessionKeyPassword creates a session key based on a shared secret (password) and the given salt 169 // and kdf parameters in the access entry 170 func NewSessionKeyPassword(password string, accessEntry *AccessEntry) ([]byte, error) { 171 if accessEntry.Type != AccessTypePass && accessEntry.Type != AccessTypeACT { 172 return nil, errors.New("incorrect access entry type") 173 174 } 175 return sessionKeyPassword(password, accessEntry.Salt, accessEntry.KdfParams) 176 } 177 178 func sessionKeyPassword(password string, salt []byte, kdfParams *KdfParams) ([]byte, error) { 179 return scrypt.Key( 180 []byte(password), 181 salt, 182 kdfParams.N, 183 kdfParams.R, 184 kdfParams.P, 185 32, 186 ) 187 } 188 189 // NewSessionKeyPK creates a new ACT Session Key using an ECDH shared secret for the given key pair and the given salt value 190 func NewSessionKeyPK(private *ecdsa.PrivateKey, public *ecdsa.PublicKey, salt []byte) ([]byte, error) { 191 granteePubEcies := ecies.ImportECDSAPublic(public) 192 privateKey := ecies.ImportECDSA(private) 193 194 bytes, err := privateKey.GenerateShared(granteePubEcies, 16, 16) 195 if err != nil { 196 return nil, err 197 } 198 bytes = append(salt, bytes...) 199 sessionKey := crypto.Keccak256(bytes) 200 return sessionKey, nil 201 } 202 203 func (a *API) doDecrypt(ctx context.Context, credentials string, pk *ecdsa.PrivateKey) DecryptFunc { 204 return func(m *ManifestEntry) error { 205 if m.Access == nil { 206 return nil 207 } 208 209 allowed := false 210 requestDomain := sctx.GetHost(ctx) 211 for _, v := range AllowedDecryptDomains { 212 if strings.Contains(requestDomain, v) { 213 allowed = true 214 } 215 } 216 217 if !allowed { 218 return ErrDecryptDomainForbidden 219 } 220 221 switch m.Access.Type { 222 case "pass": 223 if credentials != "" { 224 key, err := NewSessionKeyPassword(credentials, m.Access) 225 if err != nil { 226 return err 227 } 228 229 ref, err := hex.DecodeString(m.Hash) 230 if err != nil { 231 return err 232 } 233 234 enc := NewRefEncryption(len(ref) - 8) 235 decodedRef, err := enc.Decrypt(ref, key) 236 if err != nil { 237 return ErrDecrypt 238 } 239 240 m.Hash = hex.EncodeToString(decodedRef) 241 m.Access = nil 242 return nil 243 } 244 return ErrDecrypt 245 case "pk": 246 publisherBytes, err := hex.DecodeString(m.Access.Publisher) 247 if err != nil { 248 return ErrDecrypt 249 } 250 publisher, err := crypto.DecompressPubkey(publisherBytes) 251 if err != nil { 252 return ErrDecrypt 253 } 254 key, err := NewSessionKeyPK(pk, publisher, m.Access.Salt) 255 if err != nil { 256 return ErrDecrypt 257 } 258 ref, err := hex.DecodeString(m.Hash) 259 if err != nil { 260 return err 261 } 262 263 enc := NewRefEncryption(len(ref) - 8) 264 decodedRef, err := enc.Decrypt(ref, key) 265 if err != nil { 266 return ErrDecrypt 267 } 268 269 m.Hash = hex.EncodeToString(decodedRef) 270 m.Access = nil 271 return nil 272 case "act": 273 var ( 274 sessionKey []byte 275 err error 276 ) 277 278 publisherBytes, err := hex.DecodeString(m.Access.Publisher) 279 if err != nil { 280 return ErrDecrypt 281 } 282 publisher, err := crypto.DecompressPubkey(publisherBytes) 283 if err != nil { 284 return ErrDecrypt 285 } 286 287 sessionKey, err = NewSessionKeyPK(pk, publisher, m.Access.Salt) 288 if err != nil { 289 return ErrDecrypt 290 } 291 292 found, ciphertext, decryptionKey, err := a.getACTDecryptionKey(ctx, storage.Address(common.Hex2Bytes(m.Access.Act)), sessionKey) 293 if err != nil { 294 return err 295 } 296 if !found { 297 // try to fall back to password 298 if credentials != "" { 299 sessionKey, err = NewSessionKeyPassword(credentials, m.Access) 300 if err != nil { 301 return err 302 } 303 found, ciphertext, decryptionKey, err = a.getACTDecryptionKey(ctx, storage.Address(common.Hex2Bytes(m.Access.Act)), sessionKey) 304 if err != nil { 305 return err 306 } 307 if !found { 308 return ErrDecrypt 309 } 310 } else { 311 return ErrDecrypt 312 } 313 } 314 enc := NewRefEncryption(len(ciphertext) - 8) 315 decodedRef, err := enc.Decrypt(ciphertext, decryptionKey) 316 if err != nil { 317 return ErrDecrypt 318 } 319 320 ref, err := hex.DecodeString(m.Hash) 321 if err != nil { 322 return err 323 } 324 325 enc = NewRefEncryption(len(ref) - 8) 326 decodedMainRef, err := enc.Decrypt(ref, decodedRef) 327 if err != nil { 328 return ErrDecrypt 329 } 330 m.Hash = hex.EncodeToString(decodedMainRef) 331 m.Access = nil 332 return nil 333 } 334 return ErrUnknownAccessType 335 } 336 } 337 338 func (a *API) getACTDecryptionKey(ctx context.Context, actManifestAddress storage.Address, sessionKey []byte) (found bool, ciphertext, decryptionKey []byte, err error) { 339 hasher := sha3.NewLegacyKeccak256() 340 hasher.Write(append(sessionKey, 0)) 341 lookupKey := hasher.Sum(nil) 342 hasher.Reset() 343 344 hasher.Write(append(sessionKey, 1)) 345 accessKeyDecryptionKey := hasher.Sum(nil) 346 hasher.Reset() 347 348 lk := hex.EncodeToString(lookupKey) 349 list, err := a.GetManifestList(ctx, NOOPDecrypt, actManifestAddress, lk) 350 if err != nil { 351 return false, nil, nil, err 352 } 353 for _, v := range list.Entries { 354 if v.Path == lk { 355 cipherTextBytes, err := hex.DecodeString(v.Hash) 356 if err != nil { 357 return false, nil, nil, err 358 } 359 return true, cipherTextBytes, accessKeyDecryptionKey, nil 360 } 361 } 362 return false, nil, nil, nil 363 } 364 365 func GenerateAccessControlManifest(ctx *cli.Context, ref string, accessKey []byte, ae *AccessEntry) (*Manifest, error) { 366 refBytes, err := hex.DecodeString(ref) 367 if err != nil { 368 return nil, err 369 } 370 // encrypt ref with accessKey 371 enc := NewRefEncryption(len(refBytes)) 372 encrypted, err := enc.Encrypt(refBytes, accessKey) 373 if err != nil { 374 return nil, err 375 } 376 377 m := &Manifest{ 378 Entries: []ManifestEntry{ 379 { 380 Hash: hex.EncodeToString(encrypted), 381 ContentType: ManifestType, 382 ModTime: time.Now(), 383 Access: ae, 384 }, 385 }, 386 } 387 388 return m, nil 389 } 390 391 // DoPK is a helper function to the CLI API that handles the entire business logic for 392 // creating a session key and access entry given the cli context, ec keys and salt 393 func DoPK(ctx *cli.Context, privateKey *ecdsa.PrivateKey, granteePublicKey string, salt []byte) (sessionKey []byte, ae *AccessEntry, err error) { 394 if granteePublicKey == "" { 395 return nil, nil, errors.New("need a grantee Public Key") 396 } 397 b, err := hex.DecodeString(granteePublicKey) 398 if err != nil { 399 log.Error("error decoding grantee public key", "err", err) 400 return nil, nil, err 401 } 402 403 granteePub, err := crypto.DecompressPubkey(b) 404 if err != nil { 405 log.Error("error decompressing grantee public key", "err", err) 406 return nil, nil, err 407 } 408 409 sessionKey, err = NewSessionKeyPK(privateKey, granteePub, salt) 410 if err != nil { 411 log.Error("error getting session key", "err", err) 412 return nil, nil, err 413 } 414 415 ae, err = NewAccessEntryPK(hex.EncodeToString(crypto.CompressPubkey(&privateKey.PublicKey)), salt) 416 if err != nil { 417 log.Error("error generating access entry", "err", err) 418 return nil, nil, err 419 } 420 421 return sessionKey, ae, nil 422 } 423 424 // DoACT is a helper function to the CLI API that handles the entire business logic for 425 // creating a access key, access entry and ACT manifest (including uploading it) given the cli context, ec keys, password grantees and salt 426 func DoACT(ctx *cli.Context, privateKey *ecdsa.PrivateKey, salt []byte, grantees []string, encryptPasswords []string) (accessKey []byte, ae *AccessEntry, actManifest *Manifest, err error) { 427 if len(grantees) == 0 && len(encryptPasswords) == 0 { 428 return nil, nil, nil, errors.New("did not get any grantee public keys or any encryption passwords") 429 } 430 431 publisherPub := hex.EncodeToString(crypto.CompressPubkey(&privateKey.PublicKey)) 432 grantees = append(grantees, publisherPub) 433 434 accessKey = make([]byte, 32) 435 if _, err := io.ReadFull(rand.Reader, salt); err != nil { 436 panic("reading from crypto/rand failed: " + err.Error()) 437 } 438 if _, err := io.ReadFull(rand.Reader, accessKey); err != nil { 439 panic("reading from crypto/rand failed: " + err.Error()) 440 } 441 442 lookupPathEncryptedAccessKeyMap := make(map[string]string) 443 i := 0 444 for _, v := range grantees { 445 i++ 446 if v == "" { 447 return nil, nil, nil, errors.New("need a grantee Public Key") 448 } 449 b, err := hex.DecodeString(v) 450 if err != nil { 451 log.Error("error decoding grantee public key", "err", err) 452 return nil, nil, nil, err 453 } 454 455 granteePub, err := crypto.DecompressPubkey(b) 456 if err != nil { 457 log.Error("error decompressing grantee public key", "err", err) 458 return nil, nil, nil, err 459 } 460 sessionKey, err := NewSessionKeyPK(privateKey, granteePub, salt) 461 if err != nil { 462 return nil, nil, nil, err 463 } 464 465 hasher := sha3.NewLegacyKeccak256() 466 hasher.Write(append(sessionKey, 0)) 467 lookupKey := hasher.Sum(nil) 468 469 hasher.Reset() 470 hasher.Write(append(sessionKey, 1)) 471 472 accessKeyEncryptionKey := hasher.Sum(nil) 473 474 enc := NewRefEncryption(len(accessKey)) 475 encryptedAccessKey, err := enc.Encrypt(accessKey, accessKeyEncryptionKey) 476 if err != nil { 477 return nil, nil, nil, err 478 } 479 lookupPathEncryptedAccessKeyMap[hex.EncodeToString(lookupKey)] = hex.EncodeToString(encryptedAccessKey) 480 } 481 482 for _, pass := range encryptPasswords { 483 sessionKey, err := sessionKeyPassword(pass, salt, DefaultKdfParams) 484 if err != nil { 485 return nil, nil, nil, err 486 } 487 hasher := sha3.NewLegacyKeccak256() 488 hasher.Write(append(sessionKey, 0)) 489 lookupKey := hasher.Sum(nil) 490 491 hasher.Reset() 492 hasher.Write(append(sessionKey, 1)) 493 494 accessKeyEncryptionKey := hasher.Sum(nil) 495 496 enc := NewRefEncryption(len(accessKey)) 497 encryptedAccessKey, err := enc.Encrypt(accessKey, accessKeyEncryptionKey) 498 if err != nil { 499 return nil, nil, nil, err 500 } 501 lookupPathEncryptedAccessKeyMap[hex.EncodeToString(lookupKey)] = hex.EncodeToString(encryptedAccessKey) 502 } 503 504 m := &Manifest{ 505 Entries: []ManifestEntry{}, 506 } 507 508 for k, v := range lookupPathEncryptedAccessKeyMap { 509 m.Entries = append(m.Entries, ManifestEntry{ 510 Path: k, 511 Hash: v, 512 ContentType: "text/plain", 513 }) 514 } 515 516 ae, err = NewAccessEntryACT(hex.EncodeToString(crypto.CompressPubkey(&privateKey.PublicKey)), salt, "") 517 if err != nil { 518 return nil, nil, nil, err 519 } 520 521 return accessKey, ae, m, nil 522 } 523 524 // DoPassword is a helper function to the CLI API that handles the entire business logic for 525 // creating a session key and an access entry given the cli context, password and salt. 526 // By default - DefaultKdfParams are used as the scrypt params 527 func DoPassword(ctx *cli.Context, password string, salt []byte) (sessionKey []byte, ae *AccessEntry, err error) { 528 ae, err = NewAccessEntryPassword(salt, DefaultKdfParams) 529 if err != nil { 530 return nil, nil, err 531 } 532 533 sessionKey, err = NewSessionKeyPassword(password, ae) 534 if err != nil { 535 return nil, nil, err 536 } 537 return sessionKey, ae, nil 538 }