diff --git a/go.mod b/go.mod index a6a1e4d5..a7bffb69 100644 --- a/go.mod +++ b/go.mod @@ -7,8 +7,7 @@ go 1.13 require ( github.com/docker/docker-credential-helpers v0.6.3 github.com/emersion/go-imap v1.0.6 - github.com/jameskeane/bcrypt v0.0.0-20170924085257-7509ea014998 - golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 + github.com/jameskeane/bcrypt v0.0.0-20170924085257-7509ea014998 // indirect ) require ( @@ -17,8 +16,9 @@ require ( github.com/ProtonMail/go-autostart v0.0.0-20181114175602-c5272053443a github.com/ProtonMail/go-imap-id v0.0.0-20190926060100-f94a56b9ecde github.com/ProtonMail/go-rfc5322 v0.8.0 + github.com/ProtonMail/go-srp v0.0.0-20210514134713-bd9454f3fa01 github.com/ProtonMail/go-vcard v0.0.0-20180326232728-33aaa0a0c8a5 - github.com/ProtonMail/gopenpgp/v2 v2.1.3 + github.com/ProtonMail/gopenpgp/v2 v2.1.9 github.com/PuerkitoBio/goquery v1.5.1 github.com/abiosoft/ishell v2.0.0+incompatible github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db // indirect @@ -61,13 +61,14 @@ require ( github.com/urfave/cli/v2 v2.2.0 github.com/vmihailenco/msgpack/v5 v5.1.3 go.etcd.io/bbolt v1.3.5 + golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 golang.org/x/text v0.3.5-0.20201125200606-c27b9fd57aec + ) replace ( github.com/docker/docker-credential-helpers => github.com/ProtonMail/docker-credential-helpers v1.1.0 github.com/emersion/go-imap => github.com/ProtonMail/go-imap v0.0.0-20201228133358-4db68cea0cac - github.com/jameskeane/bcrypt => github.com/ProtonMail/bcrypt v0.0.0-20170924085257-7509ea014998 - golang.org/x/crypto => github.com/ProtonMail/crypto v0.0.0-20201112115411-41db4ea0dd1c + github.com/jameskeane/bcrypt => github.com/ProtonMail/bcrypt v0.0.0-20210511135022-227b4adcab57 ) diff --git a/go.sum b/go.sum index 8741377b..02ee7bff 100644 --- a/go.sum +++ b/go.sum @@ -8,16 +8,14 @@ github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMd github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= github.com/Masterminds/semver/v3 v3.1.0 h1:Y2lUDsFKVRSYGojLJ1yLxSXdMmMYTYls0rCvoqmMUQk= github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= -github.com/ProtonMail/bcrypt v0.0.0-20170924085257-7509ea014998 h1:YT2uVwQiRQZxCaaahwfcgTq2j3j66w00n/27gb/zubs= -github.com/ProtonMail/bcrypt v0.0.0-20170924085257-7509ea014998/go.mod h1:HecWFHognK8GfRDGnFQbW/LiV7A3MX3gZVs45vk5h8I= -github.com/ProtonMail/crypto v0.0.0-20201112115411-41db4ea0dd1c h1:iaVbEOnskSGgcH7XQWHG6VPirHDRoYe+Idd0/dl4m8A= -github.com/ProtonMail/crypto v0.0.0-20201112115411-41db4ea0dd1c/go.mod h1:Pxr7w4gA2ikI4sWyYwEffm+oew1WAJHzG1SiDpQMkrI= +github.com/ProtonMail/bcrypt v0.0.0-20210511135022-227b4adcab57 h1:pHA4K54ifoogVLunGGHi3xyF5Nz4x+Uh3dJuy3NwGQQ= +github.com/ProtonMail/bcrypt v0.0.0-20210511135022-227b4adcab57/go.mod h1:HecWFHognK8GfRDGnFQbW/LiV7A3MX3gZVs45vk5h8I= github.com/ProtonMail/docker-credential-helpers v1.1.0 h1:+kvUIpwWcbtP3WFv5sSvkFn/XLzSqPOB5AAthuk9xPk= github.com/ProtonMail/docker-credential-helpers v1.1.0/go.mod h1:mK0aBveCxhnQ756AmaTfXMZDeULvheYVhF/MWMErN5g= github.com/ProtonMail/go-autostart v0.0.0-20181114175602-c5272053443a h1:fXK2KsfnkBV9Nh+9SKzHchYjuE9s0vI20JG1mbtEAcc= github.com/ProtonMail/go-autostart v0.0.0-20181114175602-c5272053443a/go.mod h1:oTGdE7/DlWIr23G0IKW3OXK9wZ5Hw1GGiaJFccTvZi4= -github.com/ProtonMail/go-crypto v0.0.0-20201208171014-cdb7591792e2 h1:pQkjJELHayW59jp7r4G5Dlmnicr5McejDfwsjcwI1SU= -github.com/ProtonMail/go-crypto v0.0.0-20201208171014-cdb7591792e2/go.mod h1:HTM9X7e9oLwn7RiqLG0UVwVRJenLs3wN+tQ0NPAfwMQ= +github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 h1:YoJbenK9C67SkzkDfmQuVln04ygHj3vjZfd9FL+GmQQ= +github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= github.com/ProtonMail/go-imap v0.0.0-20201228133358-4db68cea0cac h1:2xU3QncAiS/W3UlWZTkbNKW5WkLzk6Egl1T0xX+sbjs= github.com/ProtonMail/go-imap v0.0.0-20201228133358-4db68cea0cac/go.mod h1:yKASt+C3ZiDAiCSssxg9caIckWF/JG7ZQTO7GAmvicU= github.com/ProtonMail/go-imap-id v0.0.0-20190926060100-f94a56b9ecde h1:5koQozTDELymYOyFbQ/VSubexAEXzDR8qGM5mO8GRdw= @@ -26,10 +24,12 @@ github.com/ProtonMail/go-mime v0.0.0-20190923161245-9b5a4261663a h1:W6RrgN/sTxg1 github.com/ProtonMail/go-mime v0.0.0-20190923161245-9b5a4261663a/go.mod h1:NYt+V3/4rEeDuaev/zw1zCq8uqVEuPHzDPo3OZrlGJ4= github.com/ProtonMail/go-rfc5322 v0.8.0 h1:7emrf75n3CDIduQflx7aT1nJa5h/kGsiFKUYX/+IAkU= github.com/ProtonMail/go-rfc5322 v0.8.0/go.mod h1:BwpTbkJxkMGkc+pC84AXZnwuWOisEULBpfPIyIKS/Us= +github.com/ProtonMail/go-srp v0.0.0-20210514134713-bd9454f3fa01 h1:sRxNvPGnJFh6yWlSr9BpGsSrshFkZLClSm5oIi++a0I= +github.com/ProtonMail/go-srp v0.0.0-20210514134713-bd9454f3fa01/go.mod h1:jOXzdvWTILIJzl83yzi/EZcnnhpI+A/5EyflaeVfi/0= github.com/ProtonMail/go-vcard v0.0.0-20180326232728-33aaa0a0c8a5 h1:Uga1DHFN4GUxuDQr0F71tpi8I9HqPIlZodZAI1lR6VQ= github.com/ProtonMail/go-vcard v0.0.0-20180326232728-33aaa0a0c8a5/go.mod h1:oeP9CMN+ajWp5jKp1kue5daJNwMMxLF+ujPaUIoJWlA= -github.com/ProtonMail/gopenpgp/v2 v2.1.3 h1:4+nFDJ9WtcUQTip/je2Ll3P21XhAUl4asWsafLrw97c= -github.com/ProtonMail/gopenpgp/v2 v2.1.3/go.mod h1:WeYndoqEcRR4/QbgRL24z6OwYX5T1RWerRk8NfZ6rJM= +github.com/ProtonMail/gopenpgp/v2 v2.1.9 h1:MdvkFBP8ldOHYOoaVct9LO+Zv5rl6VdeN1QurntRmkc= +github.com/ProtonMail/gopenpgp/v2 v2.1.9/go.mod h1:CHIXesUdnPxIxtJTg2P/cxoA0cvUwIBpZIS8SsY82QA= github.com/PuerkitoBio/goquery v1.5.1 h1:PSPBGne8NIUWw+/7vFBV+kG2J/5MOjbzc7154OaKCSE= github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= @@ -285,10 +285,21 @@ github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDf github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190910184405-b558ed863381/go.mod h1:p895TfNkDgPEmEQrNiOtIl3j98d/tGU95djDj7NfyjQ= golang.org/x/mobile v0.0.0-20200801112145-973feb4309de/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= @@ -305,13 +316,11 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -320,6 +329,7 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -327,13 +337,10 @@ golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210303074136-134d130e1a04 h1:cEhElsAv9LUt9ZUUocxzWe05oFLVd+AA2nstydTeI8g= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44 h1:Bli41pIlzTzf3KEY06n+xnzK/BESIg2ze4Pgfh/aI8c= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -347,8 +354,8 @@ golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190909214602-067311248421/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69 h1:yBHHx+XZqXJBm6Exke3N7V9gnlsyXxoCPEb1yVenjfk= golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/frontend/cli-ie/accounts.go b/internal/frontend/cli-ie/accounts.go index 8a877142..b98c0e19 100644 --- a/internal/frontend/cli-ie/accounts.go +++ b/internal/frontend/cli-ie/accounts.go @@ -68,7 +68,7 @@ func (f *frontendCLI) loginAccount(c *ishell.Context) { // nolint[funlen] } f.Println("Authenticating ... ") - client, auth, err := f.ie.Login(loginName, password) + client, auth, err := f.ie.Login(loginName, []byte(password)) if err != nil { f.processAPIError(err) return @@ -96,7 +96,7 @@ func (f *frontendCLI) loginAccount(c *ishell.Context) { // nolint[funlen] } f.Println("Adding account ...") - user, err := f.ie.FinishLogin(client, auth, mailboxPassword) + user, err := f.ie.FinishLogin(client, auth, []byte(mailboxPassword)) if err != nil { log.WithField("username", loginName).WithError(err).Error("Login was unsuccessful") f.Println("Adding account was unsuccessful:", err) diff --git a/internal/frontend/cli/accounts.go b/internal/frontend/cli/accounts.go index cc71a386..a1b7152a 100644 --- a/internal/frontend/cli/accounts.go +++ b/internal/frontend/cli/accounts.go @@ -115,7 +115,7 @@ func (f *frontendCLI) loginAccount(c *ishell.Context) { // nolint[funlen] } f.Println("Authenticating ... ") - client, auth, err := f.bridge.Login(loginName, password) + client, auth, err := f.bridge.Login(loginName, []byte(password)) if err != nil { f.processAPIError(err) return @@ -143,7 +143,7 @@ func (f *frontendCLI) loginAccount(c *ishell.Context) { // nolint[funlen] } f.Println("Adding account ...") - user, err := f.bridge.FinishLogin(client, auth, mailboxPassword) + user, err := f.bridge.FinishLogin(client, auth, []byte(mailboxPassword)) if err != nil { log.WithField("username", loginName).WithError(err).Error("Login was unsuccessful") f.Println("Adding account was unsuccessful:", err) diff --git a/internal/frontend/qt-common/accounts.go b/internal/frontend/qt-common/accounts.go index 264aa946..29609a65 100644 --- a/internal/frontend/qt-common/accounts.go +++ b/internal/frontend/qt-common/accounts.go @@ -186,7 +186,7 @@ func (a *Accounts) showLoginError(err error, scope string) bool { // 2: when has no 2FA but have MBOX func (a *Accounts) Login(login, password string) int { var err error - a.authClient, a.auth, err = a.um.Login(login, password) + a.authClient, a.auth, err = a.um.Login(login, []byte(password)) if a.showLoginError(err, "login") { return -1 } @@ -230,7 +230,7 @@ func (a *Accounts) AddAccount(mailboxPassword string) int { return -1 } - user, err := a.um.FinishLogin(a.authClient, a.auth, mailboxPassword) + user, err := a.um.FinishLogin(a.authClient, a.auth, []byte(mailboxPassword)) if err != nil { log.WithError(err).Error("Login was unsuccessful") a.qml.SetAddAccountWarning("Failure: "+err.Error(), -2) diff --git a/internal/frontend/qt/accounts.go b/internal/frontend/qt/accounts.go index 0244f6ff..cb0162c0 100644 --- a/internal/frontend/qt/accounts.go +++ b/internal/frontend/qt/accounts.go @@ -152,7 +152,7 @@ func (s *FrontendQt) showLoginError(err error, scope string) bool { // 2: when has no 2FA but have MBOX func (s *FrontendQt) login(login, password string) int { var err error - s.authClient, s.auth, err = s.bridge.Login(login, password) + s.authClient, s.auth, err = s.bridge.Login(login, []byte(password)) if s.showLoginError(err, "login") { return -1 } @@ -195,7 +195,7 @@ func (s *FrontendQt) addAccount(mailboxPassword string) int { return -1 } - user, err := s.bridge.FinishLogin(s.authClient, s.auth, mailboxPassword) + user, err := s.bridge.FinishLogin(s.authClient, s.auth, []byte(mailboxPassword)) if err != nil { log.WithError(err).Error("Login was unsuccessful") s.Qml.SetAddAccountWarning("Failure: "+err.Error(), -2) diff --git a/internal/frontend/types/types.go b/internal/frontend/types/types.go index 131e8cfa..a3edcdb7 100644 --- a/internal/frontend/types/types.go +++ b/internal/frontend/types/types.go @@ -49,8 +49,8 @@ type Updater interface { // UserManager is an interface of users needed by frontend. type UserManager interface { - Login(username, password string) (pmapi.Client, *pmapi.Auth, error) - FinishLogin(client pmapi.Client, auth *pmapi.Auth, mailboxPassword string) (User, error) + Login(username string, password []byte) (pmapi.Client, *pmapi.Auth, error) + FinishLogin(client pmapi.Client, auth *pmapi.Auth, mailboxPassword []byte) (User, error) GetUsers() []User GetUser(query string) (User, error) DeleteUser(userID string, clearCache bool) error @@ -94,7 +94,7 @@ func NewBridgeWrap(bridge *bridge.Bridge) *bridgeWrap { //nolint[golint] return &bridgeWrap{Bridge: bridge} } -func (b *bridgeWrap) FinishLogin(client pmapi.Client, auth *pmapi.Auth, mailboxPassword string) (User, error) { +func (b *bridgeWrap) FinishLogin(client pmapi.Client, auth *pmapi.Auth, mailboxPassword []byte) (User, error) { return b.Bridge.FinishLogin(client, auth, mailboxPassword) } @@ -134,7 +134,7 @@ func NewImportExportWrap(ie *importexport.ImportExport) *importExportWrap { //no return &importExportWrap{ImportExport: ie} } -func (b *importExportWrap) FinishLogin(client pmapi.Client, auth *pmapi.Auth, mailboxPassword string) (User, error) { +func (b *importExportWrap) FinishLogin(client pmapi.Client, auth *pmapi.Auth, mailboxPassword []byte) (User, error) { return b.ImportExport.FinishLogin(client, auth, mailboxPassword) } diff --git a/internal/users/credentials/credentials.go b/internal/users/credentials/credentials.go index a9dfc784..32d9e482 100644 --- a/internal/users/credentials/credentials.go +++ b/internal/users/credentials/credentials.go @@ -47,8 +47,8 @@ type Credentials struct { UserID, // Do not marshal; used as a key. Name, Emails, - APIToken, - MailboxPassword, + APIToken string + MailboxPassword []byte BridgePassword, Version string Timestamp int64 @@ -58,15 +58,15 @@ type Credentials struct { func (s *Credentials) Marshal() string { items := []string{ - s.Name, // 0 - s.Emails, // 1 - s.APIToken, // 2 - s.MailboxPassword, // 3 - s.BridgePassword, // 4 - s.Version, // 5 - "", // 6 - "", // 7 - "", // 8 + s.Name, // 0 + s.Emails, // 1 + s.APIToken, // 2 + string(s.MailboxPassword), // 3 + s.BridgePassword, // 4 + s.Version, // 5 + "", // 6 + "", // 7 + "", // 8 } items[6] = fmt.Sprint(s.Timestamp) @@ -97,7 +97,7 @@ func (s *Credentials) Unmarshal(secret string) error { s.Name = items[0] s.Emails = items[1] s.APIToken = items[2] - s.MailboxPassword = items[3] + s.MailboxPassword = []byte(items[3]) switch len(items) { case itemLengthBridge: @@ -143,11 +143,11 @@ func (s *Credentials) CheckPassword(password string) error { func (s *Credentials) Logout() { s.APIToken = "" - s.MailboxPassword = "" + s.MailboxPassword = []byte{} } func (s *Credentials) IsConnected() bool { - return s.APIToken != "" && s.MailboxPassword != "" + return s.APIToken != "" && len(s.MailboxPassword) != 0 } func (s *Credentials) SplitAPIToken() (string, string, error) { diff --git a/internal/users/credentials/credentials_test.go b/internal/users/credentials/credentials_test.go index fefe68ee..5120fceb 100644 --- a/internal/users/credentials/credentials_test.go +++ b/internal/users/credentials/credentials_test.go @@ -32,7 +32,7 @@ var wantCredentials = Credentials{ Name: "name", Emails: "email1;email2", APIToken: "token", - MailboxPassword: "mailbox pass", + MailboxPassword: []byte("mailbox pass"), BridgePassword: "bridge pass", Version: "k11", Timestamp: time.Now().Unix(), @@ -52,7 +52,7 @@ func TestUnmarshallImportExport(t *testing.T) { wantCredentials.Name, wantCredentials.Emails, wantCredentials.APIToken, - wantCredentials.MailboxPassword, + string(wantCredentials.MailboxPassword), "k11", fmt.Sprint(wantCredentials.Timestamp), } diff --git a/internal/users/credentials/store.go b/internal/users/credentials/store.go index b1599f8d..b1610d7f 100644 --- a/internal/users/credentials/store.go +++ b/internal/users/credentials/store.go @@ -39,7 +39,7 @@ func NewStore(keychain *keychain.Keychain) *Store { return &Store{secrets: keychain} } -func (s *Store) Add(userID, userName, uid, ref, mailboxPassword string, emails []string) (*Credentials, error) { +func (s *Store) Add(userID, userName, uid, ref string, mailboxPassword []byte, emails []string) (*Credentials, error) { storeLocker.Lock() defer storeLocker.Unlock() @@ -108,7 +108,7 @@ func (s *Store) UpdateEmails(userID string, emails []string) (*Credentials, erro return credentials, s.saveCredentials(credentials) } -func (s *Store) UpdatePassword(userID, password string) (*Credentials, error) { +func (s *Store) UpdatePassword(userID string, password []byte) (*Credentials, error) { storeLocker.Lock() defer storeLocker.Unlock() diff --git a/internal/users/credentials/store_test.go b/internal/users/credentials/store_test.go index bc94ccc1..658098fb 100644 --- a/internal/users/credentials/store_test.go +++ b/internal/users/credentials/store_test.go @@ -277,7 +277,7 @@ func TestMarshal(t *testing.T) { Name: "007", Emails: "ja@pm.me;aj@cus.tom", APIToken: "sdfdsfsdfsdfsdf", - MailboxPassword: "cdcdcdcd", + MailboxPassword: []byte("cdcdcdcd"), BridgePassword: "wew123", Version: "k11", Timestamp: 152469263742, diff --git a/internal/users/mocks/mocks.go b/internal/users/mocks/mocks.go index 3304b3af..8293f297 100644 --- a/internal/users/mocks/mocks.go +++ b/internal/users/mocks/mocks.go @@ -108,7 +108,7 @@ func (m *MockCredentialsStorer) EXPECT() *MockCredentialsStorerMockRecorder { } // Add mocks base method -func (m *MockCredentialsStorer) Add(arg0, arg1, arg2, arg3, arg4 string, arg5 []string) (*credentials.Credentials, error) { +func (m *MockCredentialsStorer) Add(arg0, arg1, arg2, arg3 string, arg4 []byte, arg5 []string) (*credentials.Credentials, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Add", arg0, arg1, arg2, arg3, arg4, arg5) ret0, _ := ret[0].(*credentials.Credentials) @@ -212,7 +212,7 @@ func (mr *MockCredentialsStorerMockRecorder) UpdateEmails(arg0, arg1 interface{} } // UpdatePassword mocks base method -func (m *MockCredentialsStorer) UpdatePassword(arg0, arg1 string) (*credentials.Credentials, error) { +func (m *MockCredentialsStorer) UpdatePassword(arg0 string, arg1 []byte) (*credentials.Credentials, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "UpdatePassword", arg0, arg1) ret0, _ := ret[0].(*credentials.Credentials) diff --git a/internal/users/types.go b/internal/users/types.go index 9c09985f..19c5d422 100644 --- a/internal/users/types.go +++ b/internal/users/types.go @@ -32,11 +32,11 @@ type PanicHandler interface { type CredentialsStorer interface { List() (userIDs []string, err error) - Add(userID, userName, uid, ref, mailboxPassword string, emails []string) (*credentials.Credentials, error) + Add(userID, userName, uid, ref string, mailboxPassword []byte, emails []string) (*credentials.Credentials, error) Get(userID string) (*credentials.Credentials, error) SwitchAddressMode(userID string) (*credentials.Credentials, error) UpdateEmails(userID string, emails []string) (*credentials.Credentials, error) - UpdatePassword(userID, password string) (*credentials.Credentials, error) + UpdatePassword(userID string, password []byte) (*credentials.Credentials, error) UpdateToken(userID, uid, ref string) (*credentials.Credentials, error) Logout(userID string) (*credentials.Credentials, error) Delete(userID string) error diff --git a/internal/users/user.go b/internal/users/user.go index 5641526e..ce5e7aba 100644 --- a/internal/users/user.go +++ b/internal/users/user.go @@ -227,7 +227,7 @@ func (u *User) unlockIfNecessary() error { // client. Unlock should only finish unlocking when connection is back up. // That means it should try it fast enough and not retry if connection // is still down. - err := u.client.Unlock(pmapi.ContextWithoutRetry(context.Background()), []byte(u.creds.MailboxPassword)) + err := u.client.Unlock(pmapi.ContextWithoutRetry(context.Background()), u.creds.MailboxPassword) if err == nil { return nil } @@ -364,7 +364,7 @@ func (u *User) UpdateUser(ctx context.Context) error { return err } - if err := u.client.ReloadKeys(ctx, []byte(u.creds.MailboxPassword)); err != nil { + if err := u.client.ReloadKeys(ctx, u.creds.MailboxPassword); err != nil { return errors.Wrap(err, "failed to reload keys") } diff --git a/internal/users/user_credentials_test.go b/internal/users/user_credentials_test.go index b3a13946..a38b2734 100644 --- a/internal/users/user_credentials_test.go +++ b/internal/users/user_credentials_test.go @@ -37,7 +37,7 @@ func TestUpdateUser(t *testing.T) { gomock.InOrder( m.pmapiClient.EXPECT().UpdateUser(gomock.Any()).Return(nil, nil), - m.pmapiClient.EXPECT().ReloadKeys(gomock.Any(), []byte(testCredentials.MailboxPassword)).Return(nil), + m.pmapiClient.EXPECT().ReloadKeys(gomock.Any(), testCredentials.MailboxPassword).Return(nil), m.pmapiClient.EXPECT().Addresses().Return([]*pmapi.Address{testPMAPIAddress}), m.credentialsStore.EXPECT().UpdateEmails("user", []string{testPMAPIAddress.Email}).Return(testCredentials, nil), diff --git a/internal/users/user_new_test.go b/internal/users/user_new_test.go index 50e42c8d..3b766180 100644 --- a/internal/users/user_new_test.go +++ b/internal/users/user_new_test.go @@ -46,7 +46,7 @@ func TestNewUserUnlockFails(t *testing.T) { m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil), m.pmapiClient.EXPECT().AddAuthRefreshHandler(gomock.Any()), m.pmapiClient.EXPECT().IsUnlocked().Return(false), - m.pmapiClient.EXPECT().Unlock(gomock.Any(), []byte(testCredentials.MailboxPassword)).Return(errors.New("bad password")), + m.pmapiClient.EXPECT().Unlock(gomock.Any(), testCredentials.MailboxPassword).Return(errors.New("bad password")), // Handle of unlock error. m.pmapiClient.EXPECT().AuthDelete(gomock.Any()).Return(nil), diff --git a/internal/users/users.go b/internal/users/users.go index e819d53f..9e621d04 100644 --- a/internal/users/users.go +++ b/internal/users/users.go @@ -200,14 +200,14 @@ func (u *Users) closeAllConnections() { // Login authenticates a user by username/password, returning an authorised client and an auth object. // The authorisation scope may not yet be full if the user has 2FA enabled. -func (u *Users) Login(username, password string) (authClient pmapi.Client, auth *pmapi.Auth, err error) { +func (u *Users) Login(username string, password []byte) (authClient pmapi.Client, auth *pmapi.Auth, err error) { u.crashBandicoot(username) return u.clientManager.NewClientWithLogin(context.Background(), username, password) } // FinishLogin finishes the login procedure and adds the user into the credentials store. -func (u *Users) FinishLogin(client pmapi.Client, auth *pmapi.Auth, password string) (user *User, err error) { //nolint[funlen] +func (u *Users) FinishLogin(client pmapi.Client, auth *pmapi.Auth, password []byte) (user *User, err error) { //nolint[funlen] apiUser, passphrase, err := getAPIUser(context.Background(), client, password) if err != nil { return nil, err @@ -228,7 +228,7 @@ func (u *Users) FinishLogin(client pmapi.Client, auth *pmapi.Auth, password stri } // Update the password in case the user changed it. - creds, err := u.credStorer.UpdatePassword(apiUser.ID, string(passphrase)) + creds, err := u.credStorer.UpdatePassword(apiUser.ID, passphrase) if err != nil { return nil, errors.Wrap(err, "failed to update password of user in credentials store") } @@ -264,7 +264,7 @@ func (u *Users) addNewUser(client pmapi.Client, apiUser *pmapi.User, auth *pmapi emails = client.Addresses().AllEmails() } - if _, err := u.credStorer.Add(apiUser.ID, apiUser.Name, auth.UID, auth.RefreshToken, string(passphrase), emails); err != nil { + if _, err := u.credStorer.Add(apiUser.ID, apiUser.Name, auth.UID, auth.RefreshToken, passphrase, emails); err != nil { return errors.Wrap(err, "failed to add user credentials to credentials store") } @@ -286,7 +286,7 @@ func (u *Users) addNewUser(client pmapi.Client, apiUser *pmapi.User, auth *pmapi return nil } -func getAPIUser(ctx context.Context, client pmapi.Client, password string) (*pmapi.User, []byte, error) { +func getAPIUser(ctx context.Context, client pmapi.Client, password []byte) (*pmapi.User, []byte, error) { salt, err := client.AuthSalt(ctx) if err != nil { return nil, nil, errors.Wrap(err, "failed to get salt") diff --git a/internal/users/users_login_test.go b/internal/users/users_login_test.go index 9fc6015c..8c24a9c6 100644 --- a/internal/users/users_login_test.go +++ b/internal/users/users_login_test.go @@ -37,7 +37,7 @@ func TestUsersFinishLoginBadMailboxPassword(t *testing.T) { // Set up mocks for FinishLogin. m.pmapiClient.EXPECT().AuthSalt(gomock.Any()).Return("", nil) - m.pmapiClient.EXPECT().Unlock(gomock.Any(), []byte(testCredentials.MailboxPassword)).Return(errors.New("no keys could be unlocked")) + m.pmapiClient.EXPECT().Unlock(gomock.Any(), testCredentials.MailboxPassword).Return(errors.New("no keys could be unlocked")) checkUsersFinishLogin(t, m, testAuthRefresh, testCredentials.MailboxPassword, "", ErrWrongMailboxPassword) } @@ -69,7 +69,7 @@ func TestUsersFinishLoginExistingDisconnectedUser(t *testing.T) { // Mock process of FinishLogin of already added user. gomock.InOrder( m.pmapiClient.EXPECT().AuthSalt(gomock.Any()).Return("", nil), - m.pmapiClient.EXPECT().Unlock(gomock.Any(), []byte(testCredentials.MailboxPassword)).Return(nil), + m.pmapiClient.EXPECT().Unlock(gomock.Any(), testCredentials.MailboxPassword).Return(nil), m.pmapiClient.EXPECT().CurrentUser(gomock.Any()).Return(testPMAPIUserDisconnected, nil), m.credentialsStore.EXPECT().UpdateToken(testCredentialsDisconnected.UserID, testAuthRefresh.UID, testAuthRefresh.RefreshToken).Return(testCredentials, nil), m.credentialsStore.EXPECT().UpdatePassword(testCredentialsDisconnected.UserID, testCredentials.MailboxPassword).Return(testCredentials, nil), @@ -101,7 +101,7 @@ func TestUsersFinishLoginConnectedUser(t *testing.T) { // Mock process of FinishLogin of already connected user. gomock.InOrder( m.pmapiClient.EXPECT().AuthSalt(gomock.Any()).Return("", nil), - m.pmapiClient.EXPECT().Unlock(gomock.Any(), []byte(testCredentials.MailboxPassword)).Return(nil), + m.pmapiClient.EXPECT().Unlock(gomock.Any(), testCredentials.MailboxPassword).Return(nil), m.pmapiClient.EXPECT().CurrentUser(gomock.Any()).Return(testPMAPIUser, nil), m.pmapiClient.EXPECT().AuthDelete(gomock.Any()).Return(nil), ) @@ -113,7 +113,7 @@ func TestUsersFinishLoginConnectedUser(t *testing.T) { r.EqualError(t, err, "user is already connected") } -func checkUsersFinishLogin(t *testing.T, m mocks, auth *pmapi.Auth, mailboxPassword string, expectedUserID string, expectedErr error) { +func checkUsersFinishLogin(t *testing.T, m mocks, auth *pmapi.Auth, mailboxPassword []byte, expectedUserID string, expectedErr error) { users := testNewUsers(t, m) defer cleanUpUsersData(users) diff --git a/internal/users/users_new_test.go b/internal/users/users_new_test.go index ae3e4ff7..00a7b886 100644 --- a/internal/users/users_new_test.go +++ b/internal/users/users_new_test.go @@ -84,7 +84,7 @@ func TestNewUsersWithConnectedUserWithBadToken(t *testing.T) { m.clientManager.EXPECT().NewClient("uid", "", "acc", time.Time{}).Return(m.pmapiClient) m.pmapiClient.EXPECT().AddAuthRefreshHandler(gomock.Any()) m.pmapiClient.EXPECT().IsUnlocked().Return(false) - m.pmapiClient.EXPECT().Unlock(gomock.Any(), []byte(testCredentials.MailboxPassword)).Return(errors.New("not authorized")) + m.pmapiClient.EXPECT().Unlock(gomock.Any(), testCredentials.MailboxPassword).Return(errors.New("not authorized")) m.pmapiClient.EXPECT().AuthDelete(gomock.Any()) m.credentialsStore.EXPECT().List().Return([]string{"user"}, nil) diff --git a/internal/users/users_test.go b/internal/users/users_test.go index 08971d95..962c8f7b 100644 --- a/internal/users/users_test.go +++ b/internal/users/users_test.go @@ -63,7 +63,7 @@ var ( Name: "username", Emails: "user@pm.me", APIToken: "uid:acc", - MailboxPassword: "pass", + MailboxPassword: []byte("pass"), BridgePassword: "0123456789abcdef", Version: "v1", Timestamp: 123456789, @@ -76,7 +76,7 @@ var ( Name: "usersname", Emails: "users@pm.me;anotheruser@pm.me;alsouser@pm.me", APIToken: "uid:acc", - MailboxPassword: "pass", + MailboxPassword: []byte("pass"), BridgePassword: "0123456789abcdef", Version: "v1", Timestamp: 123456789, @@ -89,7 +89,7 @@ var ( Name: "username", Emails: "user@pm.me", APIToken: "", - MailboxPassword: "", + MailboxPassword: []byte{}, BridgePassword: "0123456789abcdef", Version: "v1", Timestamp: 123456789, @@ -102,7 +102,7 @@ var ( Name: "usersname", Emails: "users@pm.me;anotheruser@pm.me;alsouser@pm.me", APIToken: "", - MailboxPassword: "", + MailboxPassword: []byte{}, BridgePassword: "0123456789abcdef", Version: "v1", Timestamp: 123456789, @@ -249,7 +249,7 @@ func mockAddingConnectedUser(m mocks) { gomock.InOrder( // Mock of users.FinishLogin. m.pmapiClient.EXPECT().AuthSalt(gomock.Any()).Return("", nil), - m.pmapiClient.EXPECT().Unlock(gomock.Any(), []byte(testCredentials.MailboxPassword)).Return(nil), + m.pmapiClient.EXPECT().Unlock(gomock.Any(), testCredentials.MailboxPassword).Return(nil), m.pmapiClient.EXPECT().CurrentUser(gomock.Any()).Return(testPMAPIUser, nil), m.pmapiClient.EXPECT().Addresses().Return([]*pmapi.Address{testPMAPIAddress}), m.credentialsStore.EXPECT().Add("user", "username", testAuthRefresh.UID, testAuthRefresh.RefreshToken, testCredentials.MailboxPassword, []string{testPMAPIAddress.Email}).Return(testCredentials, nil), diff --git a/pkg/pmapi/attachments_test.go b/pkg/pmapi/attachments_test.go index 0791d3a4..6a78fd7b 100644 --- a/pkg/pmapi/attachments_test.go +++ b/pkg/pmapi/attachments_test.go @@ -33,16 +33,24 @@ import ( pmmime "github.com/ProtonMail/proton-bridge/pkg/mime" - a "github.com/stretchr/testify/assert" - r "github.com/stretchr/testify/require" + "github.com/stretchr/testify/require" ) +const testAttachmentCleartext = `cc, +dille. +` + +// Attachment cleartext encrypted with testPrivateKeyRing. +const testKeyPacket = `wcBMA0fcZ7XLgmf2AQf/cHhfDRM9zlIuBi+h2W6DKjbbyIHMkgF6ER3JEvn/tSruUH8KTGt0N7Z+a80FFMCuXn1Y1I/nW7MVrNhGuJZAF4OymD8ugvuoAMIQX0eCYEpPXzRIWJBZg82AuowmFMsv8Dgvq4bTZq4cttI3CZcxKUNXuAearmNpmgplUKWj5USmRXK4iGB3VFGjidXkxbElrP4fD5A/rfEZ5aJgCsegqcXxX3MEjWXi9pFzgd/9phOvl1ZFm9U9hNoVAW3QsgmVeihnKaDZUyf2Qsigij21QKAUxw9U3y89eTUIqZAcmIgqeDujA3RWBgJwjtY/lOyhEmkf3AWKzehvf1xtJmCWDg==` +const testDataPacket = `0ksB6S4f4l8C1NB8yzmd/jNi0xqEZsyTDLdTP+N4Qxh3NZjla+yGRvC9rGmoUL7XVyowsG/GKTf2LXF/5E5FkX/3WMYwIv1n11ExyAE=` + var testAttachment = &Attachment{ ID: "y6uKIlc2HdoHPAwPSrvf7dXoZNMYvBgxshYUN67cY5DJjL2O8NYewuvGHcYvCfd8LpEoAI_GdymO0Jr0mHlsEw==", Name: "croutonmail.txt", Size: 77, MIMEType: "text/plain", - KeyPackets: "wcBMA0fcZ7XLgmf2AQgAiRsOlnm1kSB4/lr7tYe6pBsRGn10GqwUhrwU5PMKOHdCgnO12jO3y3CzP0Yl/jGhAYja9wLDqH8X0sk3tY32u4Sb1Qe5IuzggAiCa4dwOJj5gEFMTHMzjIMPHR7A70XqUxMhmILye8V4KRm/j4c1sxbzA1rM3lYBumQuB5l/ck0Kgt4ZqxHVXHK5Q1l65FHhSXRj8qnunasHa30TYNzP8nmBA8BinnJxpiQ7FGc2umnUhgkFtjm5ixu9vyjr9ukwDTbwAXXfmY+o7tK7kqIXJcmTL6k2UeC6Mz1AagQtRCRtU+bv/3zGojq/trZo9lom3naIeQYa36Ketmcpj2Qwjg==", + KeyPackets: testKeyPacket, + Header: textproto.MIMEHeader{ "Content-Description": {"You'll never believe what's in this text file"}, "X-Mailer": {"Microsoft Outlook 15.0", "Microsoft Live Mail 42.0"}, @@ -50,12 +58,13 @@ var testAttachment = &Attachment{ MessageID: "h3CD-DT7rLoAw1vmpcajvIPAl-wwDfXR2MHtWID3wuQURDBKTiGUAwd6E2WBbS44QQKeXImW-axm6X0hAfcVCA==", } +// Part of GET /mail/messages/{id} response from server. const testAttachmentJSON = `{ "ID": "y6uKIlc2HdoHPAwPSrvf7dXoZNMYvBgxshYUN67cY5DJjL2O8NYewuvGHcYvCfd8LpEoAI_GdymO0Jr0mHlsEw==", "Name": "croutonmail.txt", "Size": 77, "MIMEType": "text/plain", - "KeyPackets": "wcBMA0fcZ7XLgmf2AQgAiRsOlnm1kSB4/lr7tYe6pBsRGn10GqwUhrwU5PMKOHdCgnO12jO3y3CzP0Yl/jGhAYja9wLDqH8X0sk3tY32u4Sb1Qe5IuzggAiCa4dwOJj5gEFMTHMzjIMPHR7A70XqUxMhmILye8V4KRm/j4c1sxbzA1rM3lYBumQuB5l/ck0Kgt4ZqxHVXHK5Q1l65FHhSXRj8qnunasHa30TYNzP8nmBA8BinnJxpiQ7FGc2umnUhgkFtjm5ixu9vyjr9ukwDTbwAXXfmY+o7tK7kqIXJcmTL6k2UeC6Mz1AagQtRCRtU+bv/3zGojq/trZo9lom3naIeQYa36Ketmcpj2Qwjg==", + "KeyPackets": "` + testKeyPacket + `", "Headers": { "content-description": "You'll never believe what's in this text file", "x-mailer": [ @@ -66,68 +75,66 @@ const testAttachmentJSON = `{ } ` -const testAttachmentCleartext = `cc, -dille. -` - -const testAttachmentEncrypted = `wcBMA0fcZ7XLgmf2AQf/cHhfDRM9zlIuBi+h2W6DKjbbyIHMkgF6ER3JEvn/tSruUH8KTGt0N7Z+a80FFMCuXn1Y1I/nW7MVrNhGuJZAF4OymD8ugvuoAMIQX0eCYEpPXzRIWJBZg82AuowmFMsv8Dgvq4bTZq4cttI3CZcxKUNXuAearmNpmgplUKWj5USmRXK4iGB3VFGjidXkxbElrP4fD5A/rfEZ5aJgCsegqcXxX3MEjWXi9pFzgd/9phOvl1ZFm9U9hNoVAW3QsgmVeihnKaDZUyf2Qsigij21QKAUxw9U3y89eTUIqZAcmIgqeDujA3RWBgJwjtY/lOyhEmkf3AWKzehvf1xtJmCWDtJLAekuH+JfAtTQfMs5nf4zYtMahGbMkwy3Uz/jeEMYdzWY5WvshkbwvaxpqFC+11cqMLBvxik39i1xf+RORZF/91jGMCL9Z9dRMcgB` - -const testCreateAttachmentBody = `{ +// POST /mail/attachment/ response from server. +const testCreatedAttachmentBody = `{ "Code": 1000, "Attachment": {"ID": "y6uKIlc2HdoHPAwPSrvf7dXoZNMYvBgxshYUN67cY5DJjL2O8NYewuvGHcYvCfd8LpEoAI_GdymO0Jr0mHlsEw=="} }` func TestAttachment_UnmarshalJSON(t *testing.T) { + r := require.New(t) att := new(Attachment) err := json.Unmarshal([]byte(testAttachmentJSON), att) - r.NoError(t, err) + r.NoError(err) - att.MessageID = testAttachment.MessageID // This isn't in the JSON object + att.MessageID = testAttachment.MessageID // This isn't in the server response - r.Equal(t, testAttachment, att) + r.Equal(testAttachment, att) } func TestClient_CreateAttachment(t *testing.T) { + r := require.New(t) s, c := newTestClient(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - r.NoError(t, checkMethodAndPath(req, "POST", "/mail/v4/attachments")) + r.NoError(checkMethodAndPath(req, "POST", "/mail/v4/attachments")) contentType, params, err := pmmime.ParseMediaType(req.Header.Get("Content-Type")) - r.NoError(t, err) - r.Equal(t, "multipart/form-data", contentType) + r.NoError(err) + r.Equal("multipart/form-data", contentType) mr := multipart.NewReader(req.Body, params["boundary"]) form, err := mr.ReadForm(10 * 1024) - r.NoError(t, err) - defer r.NoError(t, form.RemoveAll()) + r.NoError(err) + defer r.NoError(form.RemoveAll()) - r.Equal(t, testAttachment.Name, form.Value["Filename"][0]) - r.Equal(t, testAttachment.MessageID, form.Value["MessageID"][0]) - r.Equal(t, testAttachment.MIMEType, form.Value["MIMEType"][0]) + r.Equal(testAttachment.Name, form.Value["Filename"][0]) + r.Equal(testAttachment.MessageID, form.Value["MessageID"][0]) + r.Equal(testAttachment.MIMEType, form.Value["MIMEType"][0]) dataFile, err := form.File["DataPacket"][0].Open() - r.NoError(t, err) - defer r.NoError(t, dataFile.Close()) + r.NoError(err) + defer r.NoError(dataFile.Close()) b, err := ioutil.ReadAll(dataFile) - r.NoError(t, err) - r.Equal(t, testAttachmentCleartext, string(b)) + r.NoError(err) + r.Equal(testAttachmentCleartext, string(b)) w.Header().Set("Content-Type", "application/json") - fmt.Fprint(w, testCreateAttachmentBody) + fmt.Fprint(w, testCreatedAttachmentBody) })) defer s.Close() reader := strings.NewReader(testAttachmentCleartext) // In reality, this thing is encrypted created, err := c.CreateAttachment(context.Background(), testAttachment, reader, strings.NewReader("")) - r.NoError(t, err) + r.NoError(err) - r.Equal(t, testAttachment.ID, created.ID) + r.Equal(testAttachment.ID, created.ID) } func TestClient_GetAttachment(t *testing.T) { + r := require.New(t) s, c := newTestClient(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - r.NoError(t, checkMethodAndPath(req, "GET", "/mail/v4/attachments/"+testAttachment.ID)) + r.NoError(checkMethodAndPath(req, "GET", "/mail/v4/attachments/"+testAttachment.ID)) w.Header().Set("Content-Type", "application/json") fmt.Fprint(w, testAttachmentCleartext) @@ -135,39 +142,61 @@ func TestClient_GetAttachment(t *testing.T) { defer s.Close() att, err := c.GetAttachment(context.Background(), testAttachment.ID) - r.NoError(t, err) + r.NoError(err) defer att.Close() //nolint[errcheck] // In reality, r contains encrypted data b, err := ioutil.ReadAll(att) - r.NoError(t, err) + r.NoError(err) - r.Equal(t, testAttachmentCleartext, string(b)) + r.Equal(testAttachmentCleartext, string(b)) } -func TestAttachment_Encrypt(t *testing.T) { - data := bytes.NewBufferString(testAttachmentCleartext) - r, err := testAttachment.Encrypt(testPublicKeyRing, data) - a.Nil(t, err) - b, err := ioutil.ReadAll(r) - a.Nil(t, err) +func TestAttachmentDecrypt(t *testing.T) { + r := require.New(t) - // Result is always different, so the best way is to test it by decrypting again. - // Another test for decrypting will help us to be sure it's working. - dataEnc := bytes.NewBuffer(b) - decryptAndCheck(t, dataEnc) + rawKeyPacket, err := base64.StdEncoding.DecodeString(testKeyPacket) + r.NoError(err) + + rawDataPacket, err := base64.StdEncoding.DecodeString(testDataPacket) + r.NoError(err) + + decryptAndCheck(r, bytes.NewBuffer(append(rawKeyPacket, rawDataPacket...))) } -func TestAttachment_Decrypt(t *testing.T) { - dataBytes, _ := base64.StdEncoding.DecodeString(testAttachmentEncrypted) - dataReader := bytes.NewBuffer(dataBytes) - decryptAndCheck(t, dataReader) +func TestAttachmentEncrypt(t *testing.T) { + r := require.New(t) + + encryptedReader, err := testAttachment.Encrypt( + testPublicKeyRing, + bytes.NewBufferString(testAttachmentCleartext), + ) + r.NoError(err) + + // The result is always different due to session key. The best way is to + // test result of encryption by decrypting again acn coparet to cleartext. + decryptAndCheck(r, encryptedReader) } -func decryptAndCheck(t *testing.T, data io.Reader) { - r, err := testAttachment.Decrypt(data, testPrivateKeyRing) - a.Nil(t, err) - b, err := ioutil.ReadAll(r) - a.Nil(t, err) - a.Equal(t, testAttachmentCleartext, string(b)) +func decryptAndCheck(r *require.Assertions, data io.Reader) { + // First separate KeyPacket from encrypted data. In our case keypacket + // has 271 bytes. + raw, err := ioutil.ReadAll(data) + r.NoError(err) + rawKeyPacket := raw[:271] + rawDataPacket := raw[271:] + + // KeyPacket is retrieve by get GET /mail/messages/{id} + haveAttachment := &Attachment{ + KeyPackets: base64.StdEncoding.EncodeToString(rawKeyPacket), + } + + // DataPacket is received from GET /mail/attachments/{id} + decryptedReader, err := haveAttachment.Decrypt(bytes.NewBuffer(rawDataPacket), testPrivateKeyRing) + r.NoError(err) + + b, err := ioutil.ReadAll(decryptedReader) + r.NoError(err) + + r.Equal(testAttachmentCleartext, string(b)) } diff --git a/pkg/pmapi/keyring_test.go b/pkg/pmapi/keyring_test.go index 93fe8ccd..cd01c804 100644 --- a/pkg/pmapi/keyring_test.go +++ b/pkg/pmapi/keyring_test.go @@ -22,7 +22,7 @@ import ( "testing" "github.com/ProtonMail/gopenpgp/v2/crypto" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func loadPMKeys(jsonKeys string) (keys *PMKeys) { @@ -31,6 +31,7 @@ func loadPMKeys(jsonKeys string) (keys *PMKeys) { } func TestPMKeys_GetKeyRingAndUnlock(t *testing.T) { + r := require.New(t) addrKeysWithTokens := loadPMKeys(readTestFile("keyring_addressKeysWithTokens_JSON", false)) addrKeysWithoutTokens := loadPMKeys(readTestFile("keyring_addressKeysWithoutTokens_JSON", false)) addrKeysPrimaryHasToken := loadPMKeys(readTestFile("keyring_addressKeysPrimaryHasToken_JSON", false)) @@ -42,7 +43,7 @@ func TestPMKeys_GetKeyRingAndUnlock(t *testing.T) { } userKey, err := crypto.NewKeyRing(key) - assert.NoError(t, err, "Expected not to receive an error unlocking user key") + r.NoError(err, "Expected not to receive an error unlocking user key") type args struct { userKeyring *crypto.KeyRing @@ -77,9 +78,7 @@ func TestPMKeys_GetKeyRingAndUnlock(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { kr, err := tt.keys.UnlockAll(tt.args.passphrase, tt.args.userKeyring) // nolint[scopelint] - if !assert.NoError(t, err) { - return - } + r.NoError(err) // assert at least one key has been decrypted atLeastOneDecrypted := false @@ -96,7 +95,21 @@ func TestPMKeys_GetKeyRingAndUnlock(t *testing.T) { } } - assert.True(t, atLeastOneDecrypted) + r.True(atLeastOneDecrypted) }) } } + +func TestGopenpgpEncryptAttachment(t *testing.T) { + r := require.New(t) + + wantMessage := crypto.NewPlainMessage([]byte(testAttachmentCleartext)) + + pgpSplitMessage, err := testPublicKeyRing.EncryptAttachment(wantMessage, "") + r.NoError(err) + + haveMessage, err := testPrivateKeyRing.DecryptAttachment(pgpSplitMessage) + r.NoError(err) + + r.Equal(wantMessage.Data, haveMessage.Data) +} diff --git a/pkg/pmapi/manager_auth.go b/pkg/pmapi/manager_auth.go index 7f47e34f..d99d8289 100644 --- a/pkg/pmapi/manager_auth.go +++ b/pkg/pmapi/manager_auth.go @@ -22,7 +22,7 @@ import ( "encoding/base64" "time" - "github.com/ProtonMail/proton-bridge/pkg/srp" + "github.com/ProtonMail/go-srp" ) func (m *manager) NewClient(uid, acc, ref string, exp time.Time) Client { @@ -44,7 +44,7 @@ func (m *manager) NewClientWithRefresh(ctx context.Context, uid, ref string) (Cl return c.withAuth(auth.AccessToken, auth.RefreshToken, expiresIn(auth.ExpiresIn)), auth, nil } -func (m *manager) NewClientWithLogin(ctx context.Context, username, password string) (Client, *Auth, error) { +func (m *manager) NewClientWithLogin(ctx context.Context, username string, password []byte) (Client, *Auth, error) { log.Trace("New client with login") info, err := m.getAuthInfo(ctx, GetAuthInfoReq{Username: username}) @@ -52,12 +52,12 @@ func (m *manager) NewClientWithLogin(ctx context.Context, username, password str return nil, nil, err } - srpAuth, err := srp.NewSrpAuth(info.Version, username, password, info.Salt, info.Modulus, info.ServerEphemeral) + srpAuth, err := srp.NewAuth(info.Version, username, password, info.Salt, info.Modulus, info.ServerEphemeral) if err != nil { return nil, nil, err } - proofs, err := srpAuth.GenerateSrpProofs(2048) + proofs, err := srpAuth.GenerateProofs(2048) if err != nil { return nil, nil, err } diff --git a/pkg/pmapi/manager_types.go b/pkg/pmapi/manager_types.go index 0a9e5f0d..1902be8e 100644 --- a/pkg/pmapi/manager_types.go +++ b/pkg/pmapi/manager_types.go @@ -29,7 +29,7 @@ import ( type Manager interface { NewClient(string, string, string, time.Time) Client NewClientWithRefresh(context.Context, string, string) (Client, *AuthRefresh, error) - NewClientWithLogin(context.Context, string, string) (Client, *Auth, error) + NewClientWithLogin(context.Context, string, []byte) (Client, *Auth, error) DownloadAndVerify(kr *crypto.KeyRing, url, sig string) ([]byte, error) ReportBug(context.Context, ReportBugReq) error diff --git a/pkg/pmapi/mocks/mocks.go b/pkg/pmapi/mocks/mocks.go index ac6cdaaf..78492681 100644 --- a/pkg/pmapi/mocks/mocks.go +++ b/pkg/pmapi/mocks/mocks.go @@ -670,7 +670,7 @@ func (mr *MockManagerMockRecorder) NewClient(arg0, arg1, arg2, arg3 interface{}) } // NewClientWithLogin mocks base method -func (m *MockManager) NewClientWithLogin(arg0 context.Context, arg1, arg2 string) (pmapi.Client, *pmapi.Auth, error) { +func (m *MockManager) NewClientWithLogin(arg0 context.Context, arg1 string, arg2 []byte) (pmapi.Client, *pmapi.Auth, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "NewClientWithLogin", arg0, arg1, arg2) ret0, _ := ret[0].(pmapi.Client) diff --git a/pkg/pmapi/passwords.go b/pkg/pmapi/passwords.go index f4fcc708..2547b391 100644 --- a/pkg/pmapi/passwords.go +++ b/pkg/pmapi/passwords.go @@ -20,13 +20,14 @@ package pmapi import ( "encoding/base64" - "github.com/jameskeane/bcrypt" + "github.com/ProtonMail/go-srp" "github.com/pkg/errors" ) -func HashMailboxPassword(password, salt string) ([]byte, error) { +// HashMailboxPassword expectects 128bit long salt encoded by standard base64. +func HashMailboxPassword(password []byte, salt string) ([]byte, error) { if salt == "" { - return []byte(password), nil + return password, nil } decodedSalt, err := base64.StdEncoding.DecodeString(salt) @@ -34,15 +35,10 @@ func HashMailboxPassword(password, salt string) ([]byte, error) { return nil, errors.Wrap(err, "failed to decode salt") } - encodedSalt := base64.NewEncoding("./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789").WithPadding(base64.NoPadding).EncodeToString(decodedSalt) - hashResult, err := bcrypt.Hash(password, "$2y$10$"+encodedSalt) + hash, err := srp.MailboxPassword(password, decodedSalt) if err != nil { - return nil, errors.Wrap(err, "failed to bcrypt-hash password") + return nil, errors.Wrap(err, "failed to hash password") } - if len(hashResult) != 60 { - return nil, errors.New("pmapi: invalid mailbox password hash") - } - - return []byte(hashResult[len(hashResult)-31:]), nil + return hash[len(hash)-31:], nil } diff --git a/pkg/pmapi/passwords_test.go b/pkg/pmapi/passwords_test.go new file mode 100644 index 00000000..39e139b5 --- /dev/null +++ b/pkg/pmapi/passwords_test.go @@ -0,0 +1,44 @@ +// Copyright (c) 2021 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +package pmapi + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestMailboxPassword(t *testing.T) { + // wantHash was generated with passprase and salt defined below. It + // should not change when changing implementation of the function. + wantHash := []byte("B5nwpsJQSTJ16ldr64Vdq6oeCCn32Fi") + + // Valid salt is 128bit long (16bytes) + // $echo aaaabbbbccccdddd | base64 + salt := "YWFhYWJiYmJjY2NjZGRkZAo=" + + passphrase := []byte("random") + + r := require.New(t) + _, err := HashMailboxPassword(passphrase, "badsalt") + r.Error(err) + + haveHash, err := HashMailboxPassword(passphrase, salt) + r.NoError(err) + r.Equal(wantHash, haveHash) +} diff --git a/pkg/srp/hash.go b/pkg/srp/hash.go deleted file mode 100644 index 296a9a05..00000000 --- a/pkg/srp/hash.go +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright (c) 2021 Proton Technologies AG -// -// This file is part of ProtonMail Bridge. -// -// ProtonMail Bridge is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// ProtonMail Bridge is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with ProtonMail Bridge. If not, see . - -package srp - -import ( - "bytes" - "crypto/md5" //nolint[gosec] - "crypto/sha512" - "encoding/base64" - "encoding/hex" - "errors" - "strings" - - "github.com/jameskeane/bcrypt" -) - -// BCryptHash function bcrypt algorithm to hash password with salt. -func BCryptHash(password string, salt string) (string, error) { - return bcrypt.Hash(password, salt) -} - -// ExpandHash extends the byte data for SRP flow. -func ExpandHash(data []byte) []byte { - part0 := sha512.Sum512(append(data, 0)) - part1 := sha512.Sum512(append(data, 1)) - part2 := sha512.Sum512(append(data, 2)) - part3 := sha512.Sum512(append(data, 3)) - return bytes.Join([][]byte{ - part0[:], - part1[:], - part2[:], - part3[:], - }, []byte{}) -} - -// HashPassword returns the hash of password argument. Based on version number -// following arguments are used in addition to password: -// * 0, 1, 2: userName and modulus -// * 3, 4: salt and modulus. -func HashPassword(authVersion int, password, userName string, salt, modulus []byte) ([]byte, error) { - switch authVersion { - case 4, 3: - return hashPasswordVersion3(password, salt, modulus) - case 2: - return hashPasswordVersion2(password, userName, modulus) - case 1: - return hashPasswordVersion1(password, userName, modulus) - case 0: - return hashPasswordVersion0(password, userName, modulus) - default: - return nil, errors.New("pmapi: unsupported auth version") - } -} - -// CleanUserName returns the input string in lower-case without characters `_`, -// `.` and `-`. -func CleanUserName(userName string) string { - userName = strings.ReplaceAll(userName, "-", "") - userName = strings.ReplaceAll(userName, ".", "") - userName = strings.ReplaceAll(userName, "_", "") - return strings.ToLower(userName) -} - -func hashPasswordVersion3(password string, salt, modulus []byte) (res []byte, err error) { - encodedSalt := base64.NewEncoding("./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789").WithPadding(base64.NoPadding).EncodeToString(append(salt, []byte("proton")...)) - crypted, err := BCryptHash(password, "$2y$10$"+encodedSalt) - if err != nil { - return - } - - return ExpandHash(append([]byte(crypted), modulus...)), nil -} - -func hashPasswordVersion2(password, userName string, modulus []byte) (res []byte, err error) { - return hashPasswordVersion1(password, CleanUserName(userName), modulus) -} - -func hashPasswordVersion1(password, userName string, modulus []byte) (res []byte, err error) { - prehashed := md5.Sum([]byte(strings.ToLower(userName))) //nolint[gosec] - encodedSalt := hex.EncodeToString(prehashed[:]) - crypted, err := BCryptHash(password, "$2y$10$"+encodedSalt) - if err != nil { - return - } - - return ExpandHash(append([]byte(crypted), modulus...)), nil -} - -func hashPasswordVersion0(password, userName string, modulus []byte) (res []byte, err error) { - prehashed := sha512.Sum512([]byte(password)) - return hashPasswordVersion1(base64.StdEncoding.EncodeToString(prehashed[:]), userName, modulus) -} diff --git a/pkg/srp/srp.go b/pkg/srp/srp.go deleted file mode 100644 index 70e2ea39..00000000 --- a/pkg/srp/srp.go +++ /dev/null @@ -1,219 +0,0 @@ -// Copyright (c) 2021 Proton Technologies AG -// -// This file is part of ProtonMail Bridge. -// -// ProtonMail Bridge is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// ProtonMail Bridge is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with ProtonMail Bridge. If not, see . - -package srp - -import ( - "bytes" - "crypto/rand" - "encoding/base64" - "errors" - "math/big" - - "golang.org/x/crypto/openpgp" - "golang.org/x/crypto/openpgp/clearsign" -) - -//nolint[gochecknoglobals] -var ( - ErrDataAfterModulus = errors.New("pm-srp: extra data after modulus") - ErrInvalidSignature = errors.New("pm-srp: invalid modulus signature") - RandReader = rand.Reader -) - -// Store random reader in a variable to be able to overwrite it in tests - -// Amored pubkey for modulus verification. -const modulusPubkey = `-----BEGIN PGP PUBLIC KEY BLOCK----- - -xjMEXAHLgxYJKwYBBAHaRw8BAQdAFurWXXwjTemqjD7CXjXVyKf0of7n9Ctm -L8v9enkzggHNEnByb3RvbkBzcnAubW9kdWx1c8J3BBAWCgApBQJcAcuDBgsJ -BwgDAgkQNQWFxOlRjyYEFQgKAgMWAgECGQECGwMCHgEAAPGRAP9sauJsW12U -MnTQUZpsbJb53d0Wv55mZIIiJL2XulpWPQD/V6NglBd96lZKBmInSXX/kXat -Sv+y0io+LR8i2+jV+AbOOARcAcuDEgorBgEEAZdVAQUBAQdAeJHUz1c9+KfE -kSIgcBRE3WuXC4oj5a2/U3oASExGDW4DAQgHwmEEGBYIABMFAlwBy4MJEDUF -hcTpUY8mAhsMAAD/XQD8DxNI6E78meodQI+wLsrKLeHn32iLvUqJbVDhfWSU -WO4BAMcm1u02t4VKw++ttECPt+HUgPUq5pqQWe5Q2cW4TMsE -=Y4Mw ------END PGP PUBLIC KEY BLOCK-----` - -// ReadClearSignedMessage reads the clear text from signed message and verifies -// signature. There must be no data appended after signed message in input string. -// The message must be sign by key corresponding to `modulusPubkey`. -func ReadClearSignedMessage(signedMessage string) (string, error) { - modulusBlock, rest := clearsign.Decode([]byte(signedMessage)) - if len(rest) != 0 { - return "", ErrDataAfterModulus - } - - modulusKeyring, err := openpgp.ReadArmoredKeyRing(bytes.NewReader([]byte(modulusPubkey))) - if err != nil { - return "", errors.New("pm-srp: can not read modulus pubkey") - } - - _, err = openpgp.CheckDetachedSignature(modulusKeyring, bytes.NewReader(modulusBlock.Bytes), modulusBlock.ArmoredSignature.Body, nil) - if err != nil { - return "", ErrInvalidSignature - } - - return string(modulusBlock.Bytes), nil -} - -// SrpProofs object. -type SrpProofs struct { //nolint[golint] - ClientProof, ClientEphemeral, ExpectedServerProof []byte -} - -// SrpAuth stores byte data for the calculation of SRP proofs. -type SrpAuth struct { //nolint[golint] - Modulus, ServerEphemeral, HashedPassword []byte -} - -// NewSrpAuth creates new SrpAuth from strings input. Salt and server ephemeral are in -// base64 format. Modulus is base64 with signature attached. The signature is -// verified against server key. The version controls password hash algorithm. -func NewSrpAuth(version int, username, password, salt, signedModulus, serverEphemeral string) (auth *SrpAuth, err error) { - data := &SrpAuth{} - - // Modulus - var modulus string - modulus, err = ReadClearSignedMessage(signedModulus) - if err != nil { - return - } - data.Modulus, err = base64.StdEncoding.DecodeString(modulus) - if err != nil { - return - } - - // Password - var decodedSalt []byte - if version >= 3 { - decodedSalt, err = base64.StdEncoding.DecodeString(salt) - if err != nil { - return - } - } - data.HashedPassword, err = HashPassword(version, password, username, decodedSalt, data.Modulus) - if err != nil { - return - } - - // Server ephermeral - data.ServerEphemeral, err = base64.StdEncoding.DecodeString(serverEphemeral) - if err != nil { - return - } - - return data, nil -} - -// GenerateSrpProofs calculates SPR proofs. -func (s *SrpAuth) GenerateSrpProofs(length int) (res *SrpProofs, err error) { //nolint[funlen] - toInt := func(arr []byte) *big.Int { - var reversed = make([]byte, len(arr)) - for i := 0; i < len(arr); i++ { - reversed[len(arr)-i-1] = arr[i] - } - return big.NewInt(0).SetBytes(reversed) - } - - fromInt := func(num *big.Int) []byte { - var arr = num.Bytes() - var reversed = make([]byte, length/8) - for i := 0; i < len(arr); i++ { - reversed[len(arr)-i-1] = arr[i] - } - return reversed - } - - generator := big.NewInt(2) - multiplier := toInt(ExpandHash(append(fromInt(generator), s.Modulus...))) - - modulus := toInt(s.Modulus) - serverEphemeral := toInt(s.ServerEphemeral) - hashedPassword := toInt(s.HashedPassword) - - modulusMinusOne := big.NewInt(0).Sub(modulus, big.NewInt(1)) - - if modulus.BitLen() != length { - return nil, errors.New("pm-srp: SRP modulus has incorrect size") - } - - multiplier = multiplier.Mod(multiplier, modulus) - - if multiplier.Cmp(big.NewInt(1)) <= 0 || multiplier.Cmp(modulusMinusOne) >= 0 { - return nil, errors.New("pm-srp: SRP multiplier is out of bounds") - } - - if generator.Cmp(big.NewInt(1)) <= 0 || generator.Cmp(modulusMinusOne) >= 0 { - return nil, errors.New("pm-srp: SRP generator is out of bounds") - } - - if serverEphemeral.Cmp(big.NewInt(1)) <= 0 || serverEphemeral.Cmp(modulusMinusOne) >= 0 { - return nil, errors.New("pm-srp: SRP server ephemeral is out of bounds") - } - - // Check primality - // Doing exponentiation here is faster than a full call to ProbablyPrime while - // still perfectly accurate by Pocklington's theorem - if big.NewInt(0).Exp(big.NewInt(2), modulusMinusOne, modulus).Cmp(big.NewInt(1)) != 0 { - return nil, errors.New("pm-srp: SRP modulus is not prime") - } - - // Check safe primality - if !big.NewInt(0).Rsh(modulus, 1).ProbablyPrime(10) { - return nil, errors.New("pm-srp: SRP modulus is not a safe prime") - } - - var clientSecret, clientEphemeral, scramblingParam *big.Int - for { - for { - clientSecret, err = rand.Int(RandReader, modulusMinusOne) - if err != nil { - return - } - - if clientSecret.Cmp(big.NewInt(int64(length*2))) > 0 { // Very likely - break - } - } - - clientEphemeral = big.NewInt(0).Exp(generator, clientSecret, modulus) - scramblingParam = toInt(ExpandHash(append(fromInt(clientEphemeral), fromInt(serverEphemeral)...))) - if scramblingParam.Cmp(big.NewInt(0)) != 0 { // Very likely - break - } - } - - subtracted := big.NewInt(0).Sub(serverEphemeral, big.NewInt(0).Mod(big.NewInt(0).Mul(big.NewInt(0).Exp(generator, hashedPassword, modulus), multiplier), modulus)) - if subtracted.Cmp(big.NewInt(0)) < 0 { - subtracted.Add(subtracted, modulus) - } - exponent := big.NewInt(0).Mod(big.NewInt(0).Add(big.NewInt(0).Mul(scramblingParam, hashedPassword), clientSecret), modulusMinusOne) - sharedSession := big.NewInt(0).Exp(subtracted, exponent, modulus) - - clientProof := ExpandHash(bytes.Join([][]byte{fromInt(clientEphemeral), fromInt(serverEphemeral), fromInt(sharedSession)}, []byte{})) - serverProof := ExpandHash(bytes.Join([][]byte{fromInt(clientEphemeral), clientProof, fromInt(sharedSession)}, []byte{})) - - return &SrpProofs{ClientEphemeral: fromInt(clientEphemeral), ClientProof: clientProof, ExpectedServerProof: serverProof}, nil -} - -// GenerateVerifier verifier for update pwds and create accounts. -func (s *SrpAuth) GenerateVerifier(length int) ([]byte, error) { - return nil, errors.New("pm-srp: the client doesn't need SRP GenerateVerifier") -} diff --git a/pkg/srp/srp_test.go b/pkg/srp/srp_test.go deleted file mode 100644 index df9152c8..00000000 --- a/pkg/srp/srp_test.go +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) 2021 Proton Technologies AG -// -// This file is part of ProtonMail Bridge. -// -// ProtonMail Bridge is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// ProtonMail Bridge is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with ProtonMail Bridge. If not, see . - -package srp - -import ( - "bytes" - "encoding/base64" - "math/rand" - "testing" -) - -const ( - testServerEphemeral = "l13IQSVFBEV0ZZREuRQ4ZgP6OpGiIfIjbSDYQG3Yp39FkT2B/k3n1ZhwqrAdy+qvPPFq/le0b7UDtayoX4aOTJihoRvifas8Hr3icd9nAHqd0TUBbkZkT6Iy6UpzmirCXQtEhvGQIdOLuwvy+vZWh24G2ahBM75dAqwkP961EJMh67/I5PA5hJdQZjdPT5luCyVa7BS1d9ZdmuR0/VCjUOdJbYjgtIH7BQoZs+KacjhUN8gybu+fsycvTK3eC+9mCN2Y6GdsuCMuR3pFB0RF9eKae7cA6RbJfF1bjm0nNfWLXzgKguKBOeF3GEAsnCgK68q82/pq9etiUDizUlUBcA==" - testServerProof = "ffYFIhnhZJAflFJr9FfXbtdsBLkDGH+TUR5sj98wg0iVHyIhIVT6BeZD8tZA75tYlz7uYIanswweB3bjrGfITXfxERgQysQSoPUB284cX4VQm1IfTB/9LPma618MH8OULNluXVu2eizPWnvIn9VLXCaIX+38Xd6xOjmCQgfkpJy3Sh3ndikjqNCGWiKyvERVJi0nTmpAbHmcdeEp1K++ZRbebRhm2d018o/u4H2gu+MF39Hx12zMzEGNMwkNkgKSEQYlqmj57S6tW9JuB30zVZFnw6Krftg1QfJR6zCT1/J57OGp0A/7X/lC6Xz/I33eJvXOpG9GCRCbNiozFg9IXQ==" - - testClientProof = "8dQtp6zIeEmu3D93CxPdEiCWiAE86uDmK33EpxyqReMwUrm/bTL+zCkWa/X7QgLNrt2FBAriyROhz5TEONgZq/PqZnBEBym6Rvo708KHu6S4LFdZkVc0+lgi7yQpNhU8bqB0BCqdSWd3Fjd3xbOYgO7/vnFK+p9XQZKwEh2RmGv97XHwoxefoyXK6BB+VVMkELd4vL7vdqBiOBU3ufOlSp+0XBMVltQ4oi5l1y21pzOA9cw5WTPIPMcQHffNFq/rReHYnqbBqiLlSLyw6K0PcVuN3bvr3rVYfdS1CsM/Rv1DzXlBUl39B2j82y6hdyGcTeplGyAnAcu0CimvynKBvQ==" - testModulus = "W2z5HBi8RvsfYzZTS7qBaUxxPhsfHJFZpu3Kd6s1JafNrCCH9rfvPLrfuqocxWPgWDH2R8neK7PkNvjxto9TStuY5z7jAzWRvFWN9cQhAKkdWgy0JY6ywVn22+HFpF4cYesHrqFIKUPDMSSIlWjBVmEJZ/MusD44ZT29xcPrOqeZvwtCffKtGAIjLYPZIEbZKnDM1Dm3q2K/xS5h+xdhjnndhsrkwm9U9oyA2wxzSXFL+pdfj2fOdRwuR5nW0J2NFrq3kJjkRmpO/Genq1UW+TEknIWAb6VzJJJA244K/H8cnSx2+nSNZO3bbo6Ys228ruV9A8m6DhxmS+bihN3ttQ==" - testModulusClearSign = `-----BEGIN PGP SIGNED MESSAGE----- -Hash: SHA256 - -W2z5HBi8RvsfYzZTS7qBaUxxPhsfHJFZpu3Kd6s1JafNrCCH9rfvPLrfuqocxWPgWDH2R8neK7PkNvjxto9TStuY5z7jAzWRvFWN9cQhAKkdWgy0JY6ywVn22+HFpF4cYesHrqFIKUPDMSSIlWjBVmEJZ/MusD44ZT29xcPrOqeZvwtCffKtGAIjLYPZIEbZKnDM1Dm3q2K/xS5h+xdhjnndhsrkwm9U9oyA2wxzSXFL+pdfj2fOdRwuR5nW0J2NFrq3kJjkRmpO/Genq1UW+TEknIWAb6VzJJJA244K/H8cnSx2+nSNZO3bbo6Ys228ruV9A8m6DhxmS+bihN3ttQ== ------BEGIN PGP SIGNATURE----- -Version: ProtonMail -Comment: https://protonmail.com - -wl4EARYIABAFAlwB1j0JEDUFhcTpUY8mAAD8CgEAnsFnF4cF0uSHKkXa1GIa -GO86yMV4zDZEZcDSJo0fgr8A/AlupGN9EdHlsrZLmTA1vhIx+rOgxdEff28N -kvNM7qIK -=q6vu ------END PGP SIGNATURE-----` -) - -func init() { - // Only for tests, replace the default random reader by something that always - // return the same thing - RandReader = rand.New(rand.NewSource(42)) -} - -func TestReadClearSigned(t *testing.T) { - cleartext, err := ReadClearSignedMessage(testModulusClearSign) - if err != nil { - t.Fatal("Expected no error but have ", err) - } - if cleartext != testModulus { - t.Fatalf("Expected message\n\t'%s'\nbut have\n\t'%s'", testModulus, cleartext) - } - - lastChar := len(testModulusClearSign) - wrongSignature := testModulusClearSign[:lastChar-100] - wrongSignature += "c" - wrongSignature += testModulusClearSign[lastChar-99:] - _, err = ReadClearSignedMessage(wrongSignature) - if err != ErrInvalidSignature { - t.Fatal("Expected the ErrInvalidSignature but have ", err) - } - - wrongSignature = testModulusClearSign + "data after modulus" - _, err = ReadClearSignedMessage(wrongSignature) - if err != ErrDataAfterModulus { - t.Fatal("Expected the ErrDataAfterModulus but have ", err) - } -} - -func TestSRPauth(t *testing.T) { - srp, err := NewSrpAuth(4, "bridgetest", "test", "yKlc5/CvObfoiw==", testModulusClearSign, testServerEphemeral) - if err != nil { - t.Fatal("Expected no error but have ", err) - } - - proofs, err := srp.GenerateSrpProofs(2048) - if err != nil { - t.Fatal("Expected no error but have ", err) - } - - expectedProof, err := base64.StdEncoding.DecodeString(testServerProof) - if err != nil { - t.Fatal("Expected no error but have ", err) - } - if !bytes.Equal(proofs.ExpectedServerProof, expectedProof) { - t.Fatalf("Expected server proof\n\t'%s'\nbut have\n\t'%s'", - testServerProof, - base64.StdEncoding.EncodeToString(proofs.ExpectedServerProof), - ) - } - - expectedProof, err = base64.StdEncoding.DecodeString(testClientProof) - if err != nil { - t.Fatal("Expected no error but have ", err) - } - if !bytes.Equal(proofs.ClientProof, expectedProof) { - t.Fatalf("Expected client proof\n\t'%s'\nbut have\n\t'%s'", - testClientProof, - base64.StdEncoding.EncodeToString(proofs.ClientProof), - ) - } -} diff --git a/test/accounts/account.go b/test/accounts/account.go index 9fedcaea..975ae1f2 100644 --- a/test/accounts/account.go +++ b/test/accounts/account.go @@ -177,12 +177,12 @@ func (a *TestAccount) EnsureAddress(addressOrAddressTestID string) string { return addressOrAddressTestID } -func (a *TestAccount) Password() string { - return a.password +func (a *TestAccount) Password() []byte { + return []byte(a.password) } -func (a *TestAccount) MailboxPassword() string { - return a.mailboxPassword +func (a *TestAccount) MailboxPassword() []byte { + return []byte(a.mailboxPassword) } func (a *TestAccount) IsTwoFAEnabled() bool { diff --git a/test/context/credentials.go b/test/context/credentials.go index 0fc409cc..500bdff3 100644 --- a/test/context/credentials.go +++ b/test/context/credentials.go @@ -51,7 +51,7 @@ func (c *fakeCredStore) List() (userIDs []string, err error) { return keys, nil } -func (c *fakeCredStore) Add(userID, userName, uid, ref, mailboxPassword string, emails []string) (*credentials.Credentials, error) { +func (c *fakeCredStore) Add(userID, userName, uid, ref string, mailboxPassword []byte, emails []string) (*credentials.Credentials, error) { bridgePassword := bridgePassword if c, ok := c.credentials[userID]; ok { bridgePassword = c.BridgePassword @@ -80,7 +80,7 @@ func (c *fakeCredStore) UpdateEmails(userID string, emails []string) (*credentia return c.credentials[userID], nil } -func (c *fakeCredStore) UpdatePassword(userID, password string) (*credentials.Credentials, error) { +func (c *fakeCredStore) UpdatePassword(userID string, password []byte) (*credentials.Credentials, error) { creds, err := c.Get(userID) if err != nil { return nil, err @@ -100,7 +100,7 @@ func (c *fakeCredStore) UpdateToken(userID, uid, ref string) (*credentials.Crede func (c *fakeCredStore) Logout(userID string) (*credentials.Credentials, error) { c.credentials[userID].APIToken = "" - c.credentials[userID].MailboxPassword = "" + c.credentials[userID].MailboxPassword = []byte{} return c.credentials[userID], nil } diff --git a/test/context/pmapi_controller.go b/test/context/pmapi_controller.go index bc768496..17c5a1b9 100644 --- a/test/context/pmapi_controller.go +++ b/test/context/pmapi_controller.go @@ -30,7 +30,7 @@ import ( type PMAPIController interface { TurnInternetConnectionOff() TurnInternetConnectionOn() - AddUser(user *pmapi.User, addresses *pmapi.AddressList, password string, twoFAEnabled bool) error + AddUser(user *pmapi.User, addresses *pmapi.AddressList, password []byte, twoFAEnabled bool) error AddUserLabel(username string, label *pmapi.Label) error GetLabelIDs(username string, labelNames []string) ([]string, error) AddUserMessage(username string, message *pmapi.Message) (string, error) diff --git a/test/context/users.go b/test/context/users.go index 8beaafbe..9fa66ec0 100644 --- a/test/context/users.go +++ b/test/context/users.go @@ -24,9 +24,9 @@ import ( "path/filepath" "time" + "github.com/ProtonMail/go-srp" "github.com/ProtonMail/proton-bridge/internal/store" "github.com/ProtonMail/proton-bridge/internal/users" - "github.com/ProtonMail/proton-bridge/pkg/srp" "github.com/pkg/errors" "github.com/stretchr/testify/assert" ) @@ -37,7 +37,7 @@ func (ctx *TestContext) GetUsers() *users.Users { } // LoginUser logs in the user with the given username, password, and mailbox password. -func (ctx *TestContext) LoginUser(username, password, mailboxPassword string) error { +func (ctx *TestContext) LoginUser(username string, password, mailboxPassword []byte) error { srp.RandReader = rand.New(rand.NewSource(42)) //nolint[gosec] It is OK to use weaker random number generator here client, auth, err := ctx.users.Login(username, password) diff --git a/test/fakeapi/controller_control.go b/test/fakeapi/controller_control.go index 4d2f92cf..35a9e184 100644 --- a/test/fakeapi/controller_control.go +++ b/test/fakeapi/controller_control.go @@ -61,7 +61,7 @@ func (ctl *Controller) ReorderAddresses(user *pmapi.User, addressIDs []string) e return api.ReorderAddresses(context.Background(), addressIDs) } -func (ctl *Controller) AddUser(user *pmapi.User, addresses *pmapi.AddressList, password string, twoFAEnabled bool) error { +func (ctl *Controller) AddUser(user *pmapi.User, addresses *pmapi.AddressList, password []byte, twoFAEnabled bool) error { ctl.usersByUsername[user.Name] = &fakeUser{ user: user, password: password, diff --git a/test/fakeapi/controller_session.go b/test/fakeapi/controller_session.go index 6a22b18a..87de705f 100644 --- a/test/fakeapi/controller_session.go +++ b/test/fakeapi/controller_session.go @@ -18,6 +18,7 @@ package fakeapi import ( + "bytes" "errors" "github.com/ProtonMail/proton-bridge/pkg/pmapi" @@ -49,10 +50,10 @@ func (ctl *Controller) checkScope(uid string) bool { return session.hasFullScope } -func (ctl *Controller) createSessionIfAuthorized(username, password string) (*fakeSession, error) { +func (ctl *Controller) createSessionIfAuthorized(username string, password []byte) (*fakeSession, error) { // get user user, ok := ctl.usersByUsername[username] - if !ok || user.password != password { + if !ok || !bytes.Equal(user.password, password) { return nil, errWrongNameOrPassword } diff --git a/test/fakeapi/controller_user.go b/test/fakeapi/controller_user.go index 3b73a542..5524e55e 100644 --- a/test/fakeapi/controller_user.go +++ b/test/fakeapi/controller_user.go @@ -21,6 +21,6 @@ import "github.com/ProtonMail/proton-bridge/pkg/pmapi" type fakeUser struct { user *pmapi.User - password string + password []byte has2FA bool } diff --git a/test/fakeapi/manager.go b/test/fakeapi/manager.go index 71da9d05..14eca80e 100644 --- a/test/fakeapi/manager.go +++ b/test/fakeapi/manager.go @@ -94,7 +94,7 @@ func (m *fakePMAPIManager) NewClientWithRefresh(_ context.Context, uid, ref stri return client, auth, nil } -func (m *fakePMAPIManager) NewClientWithLogin(_ context.Context, username string, password string) (pmapi.Client, *pmapi.Auth, error) { +func (m *fakePMAPIManager) NewClientWithLogin(_ context.Context, username string, password []byte) (pmapi.Client, *pmapi.Auth, error) { if err := m.controller.checkAndRecordCall(POST, "/auth/info", &pmapi.GetAuthInfoReq{Username: username}); err != nil { return nil, nil, err } diff --git a/test/liveapi/users.go b/test/liveapi/users.go index 7f328329..eac77afa 100644 --- a/test/liveapi/users.go +++ b/test/liveapi/users.go @@ -25,7 +25,7 @@ import ( "github.com/pkg/errors" ) -func (ctl *Controller) AddUser(user *pmapi.User, addresses *pmapi.AddressList, password string, twoFAEnabled bool) error { +func (ctl *Controller) AddUser(user *pmapi.User, addresses *pmapi.AddressList, password []byte, twoFAEnabled bool) error { if twoFAEnabled { return godog.ErrPending } diff --git a/test/users_actions_test.go b/test/users_actions_test.go index 0d6e8634..1f7f983c 100644 --- a/test/users_actions_test.go +++ b/test/users_actions_test.go @@ -45,7 +45,7 @@ func userLogsInWithBadPassword(bddUserID string) error { if account == nil { return godog.ErrPending } - ctx.SetLastError(ctx.LoginUser(account.Username(), "you shall not pass!", "123")) + ctx.SetLastError(ctx.LoginUser(account.Username(), []byte("you shall not pass!"), []byte("123"))) return nil }