github.com/decred/dcrlnd@v0.7.6/walletunlocker/dcrlnd_service.go (about) 1 package walletunlocker 2 3 import ( 4 "context" 5 "crypto/tls" 6 "crypto/x509" 7 "fmt" 8 "io/ioutil" 9 10 pb "decred.org/dcrwallet/v4/rpc/walletrpc" 11 "github.com/decred/dcrd/hdkeychain/v3" 12 "github.com/decred/dcrlnd/lnrpc" 13 "google.golang.org/grpc" 14 "google.golang.org/grpc/credentials" 15 ) 16 17 func tlsCertFromFile(fname string) (*x509.CertPool, error) { 18 b, err := ioutil.ReadFile(fname) 19 if err != nil { 20 return nil, err 21 } 22 cp := x509.NewCertPool() 23 if !cp.AppendCertsFromPEM(b) { 24 return nil, fmt.Errorf("credentials: failed to append certificates") 25 } 26 27 return cp, nil 28 } 29 30 // UnlockRemoteWallet sends the password provided by the incoming 31 // UnlockRemoteWalletRequest over the UnlockMsgs channel in case it 32 // successfully decrypts an existing remote wallet. 33 func (u *UnlockerService) unlockRemoteWallet(ctx context.Context, 34 in *lnrpc.UnlockWalletRequest) (*lnrpc.UnlockWalletResponse, error) { 35 36 db := u.db.Load() 37 if db == nil { 38 return nil, fmt.Errorf("attempting to unlock before unlocker service has DB") 39 } 40 41 // dcrwallet rpc.cert. 42 caCert, err := tlsCertFromFile(u.dcrwCert) 43 if err != nil { 44 return nil, err 45 } 46 47 // Figure out the client cert. 48 var clientCerts []tls.Certificate 49 if in.DcrwClientKeyCert != nil { 50 // Received a key+cert blob from dcrwallet ipc. X509KeyPair is 51 // smart enough to decode the correct element from the 52 // concatenated blob, so it's ok to pass the same byte slice in 53 // both arguments. 54 cert, err := tls.X509KeyPair(in.DcrwClientKeyCert, in.DcrwClientKeyCert) 55 if err != nil { 56 return nil, err 57 } 58 clientCerts = []tls.Certificate{cert} 59 } else if u.dcrwClientKey != "" && u.dcrwClientCert != "" { 60 cert, err := tls.LoadX509KeyPair(u.dcrwClientCert, u.dcrwClientKey) 61 if err != nil { 62 return nil, err 63 } 64 clientCerts = []tls.Certificate{cert} 65 } 66 67 tlsCfg := &tls.Config{ 68 ServerName: "localhost", 69 RootCAs: caCert, 70 Certificates: clientCerts, 71 } 72 creds := credentials.NewTLS(tlsCfg) 73 74 // Connect to the wallet. 75 conn, err := grpc.Dial(u.dcrwHost, grpc.WithTransportCredentials(creds)) 76 if err != nil { 77 return nil, err 78 } 79 80 wallet := pb.NewWalletServiceClient(conn) 81 82 // Unlock the account. 83 unlockAcctReq := &pb.UnlockAccountRequest{ 84 AccountNumber: uint32(u.dcrwAccount), 85 Passphrase: in.WalletPassword, 86 } 87 _, err = wallet.UnlockAccount(ctx, unlockAcctReq) 88 if err != nil { 89 return nil, fmt.Errorf("unable to unlock account: %v", err) 90 } 91 92 // Ensure we can grab the privkey for the given account. 93 getAcctReq := &pb.GetAccountExtendedPrivKeyRequest{ 94 AccountNumber: uint32(u.dcrwAccount), 95 } 96 getAcctResp, err := wallet.GetAccountExtendedPrivKey(ctx, getAcctReq) 97 98 // Irrespective of the return of GetAccountExtendedPrivKey, re-lock the 99 // account. 100 lockAcctReq := &pb.LockAccountRequest{ 101 AccountNumber: uint32(u.dcrwAccount), 102 } 103 _, lockErr := wallet.LockAccount(ctx, lockAcctReq) 104 if lockErr != nil { 105 log.Errorf("Error while locking account number %d: %v", 106 u.dcrwAccount, lockErr) 107 } 108 109 // And now check if GetAccountExtendedPrivKey returned an error. 110 if err != nil { 111 return nil, fmt.Errorf("unable to get xpriv: %v", err) 112 } 113 114 // Ensure we don't attempt to use a keyring derived from a different 115 // account than previously used by comparing the first external public 116 // key with the one stored in the database. 117 acctXPriv, err := hdkeychain.NewKeyFromString( 118 getAcctResp.AccExtendedPrivKey, u.netParams, 119 ) 120 if err != nil { 121 return nil, fmt.Errorf("unable to create account xpriv: %v", err) 122 } 123 124 branchExtXPriv, err := acctXPriv.Child(0) 125 if err != nil { 126 return nil, fmt.Errorf("unable to derive the external branch xpriv: %v", err) 127 } 128 129 firstKey, err := branchExtXPriv.Child(0) 130 if err != nil { 131 return nil, fmt.Errorf("unable to derive first external key: %v", err) 132 } 133 firstPubKeyBytes := firstKey.SerializedPubKey() 134 if err = db.CompareAndStoreAccountID(firstPubKeyBytes); err != nil { 135 return nil, fmt.Errorf("account number %d failed to generate "+ 136 "previously stored account ID: %v", u.dcrwAccount, err) 137 } 138 139 // We successfully opened the wallet and pass the instance back to 140 // avoid it needing to be unlocked again. 141 walletUnlockMsg := &WalletUnlockMsg{ 142 Passphrase: in.WalletPassword, 143 Conn: conn, 144 UnloadWallet: func() error { return nil }, 145 StatelessInit: in.StatelessInit, 146 } 147 148 // Before we return the unlock payload, we'll check if we can extract 149 // any channel backups to pass up to the higher level sub-system. 150 chansToRestore := extractChanBackups(in.ChannelBackups) 151 if chansToRestore != nil { 152 walletUnlockMsg.ChanBackups = *chansToRestore 153 } 154 155 // At this point we were able to open the existing wallet with the 156 // provided password. We send the password over the UnlockMsgs channel, 157 // such that it can be used by lnd to open the wallet. 158 select { 159 case u.UnlockMsgs <- walletUnlockMsg: 160 // We need to read from the channel to let the daemon continue 161 // its work. But we don't need the returned macaroon for this 162 // operation, so we read it but then discard it. 163 select { 164 case adminMac := <-u.MacResponseChan: 165 return &lnrpc.UnlockWalletResponse{ 166 AdminMacaroon: adminMac, 167 }, nil 168 169 case <-ctx.Done(): 170 return nil, ErrUnlockTimeout 171 } 172 173 case <-ctx.Done(): 174 return nil, ErrUnlockTimeout 175 } 176 }