mirror of
https://github.com/go-acme/lego.git
synced 2024-11-25 00:56:20 +02:00
nearlyfreespeech: fix authentication (#1999)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
This commit is contained in:
parent
406dad30fe
commit
a423bb7411
@ -28,6 +28,8 @@ type Client struct {
|
||||
login string
|
||||
apiKey string
|
||||
|
||||
signer *Signer
|
||||
|
||||
baseURL *url.URL
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
@ -38,6 +40,7 @@ func NewClient(login string, apiKey string) *Client {
|
||||
return &Client{
|
||||
login: login,
|
||||
apiKey: apiKey,
|
||||
signer: NewSigner(),
|
||||
baseURL: baseURL,
|
||||
HTTPClient: &http.Client{Timeout: 10 * time.Second},
|
||||
}
|
||||
@ -74,7 +77,7 @@ func (c Client) doRequest(ctx context.Context, endpoint *url.URL, params url.Val
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
req.Header.Set(authenticationHeader, c.createSignature(endpoint.Path, payload))
|
||||
req.Header.Set(authenticationHeader, c.signer.Sign(endpoint.Path, payload, c.login, c.apiKey))
|
||||
|
||||
resp, err := c.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
@ -90,25 +93,6 @@ func (c Client) doRequest(ctx context.Context, endpoint *url.URL, params url.Val
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c Client) createSignature(uri string, body string) string {
|
||||
// This is the only part of this that needs to be serialized.
|
||||
salt := make([]byte, 16)
|
||||
for i := 0; i < 16; i++ {
|
||||
salt[i] = saltBytes[rand.Intn(len(saltBytes))]
|
||||
}
|
||||
|
||||
// Header is "login;timestamp;salt;hash".
|
||||
// hash is SHA1("login;timestamp;salt;api-key;request-uri;body-hash")
|
||||
// and body-hash is SHA1(body).
|
||||
|
||||
bodyHash := sha1.Sum([]byte(body))
|
||||
timestamp := strconv.FormatInt(time.Now().Unix(), 10)
|
||||
|
||||
hashInput := fmt.Sprintf("%s;%s;%s;%s;%s;%02x", c.login, timestamp, salt, c.apiKey, uri, bodyHash)
|
||||
|
||||
return fmt.Sprintf("%s;%s;%s;%02x", c.login, timestamp, salt, sha1.Sum([]byte(hashInput)))
|
||||
}
|
||||
|
||||
func parseError(req *http.Request, resp *http.Response) error {
|
||||
raw, _ := io.ReadAll(resp.Body)
|
||||
|
||||
@ -120,3 +104,38 @@ func parseError(req *http.Request, resp *http.Response) error {
|
||||
|
||||
return errAPI
|
||||
}
|
||||
|
||||
type Signer struct {
|
||||
saltShaker func() []byte
|
||||
clock func() time.Time
|
||||
}
|
||||
|
||||
func NewSigner() *Signer {
|
||||
return &Signer{saltShaker: getRandomSalt, clock: time.Now}
|
||||
}
|
||||
|
||||
func (c Signer) Sign(uri string, body, login, apiKey string) string {
|
||||
// Header is "login;timestamp;salt;hash".
|
||||
// hash is SHA1("login;timestamp;salt;api-key;request-uri;body-hash")
|
||||
// and body-hash is SHA1(body).
|
||||
|
||||
bodyHash := sha1.Sum([]byte(body))
|
||||
timestamp := strconv.FormatInt(c.clock().Unix(), 10)
|
||||
|
||||
// Workaround for https://golang.org/issue/58605
|
||||
uri = "/" + strings.TrimLeft(uri, "/")
|
||||
|
||||
hashInput := fmt.Sprintf("%s;%s;%s;%s;%s;%02x", login, timestamp, c.saltShaker(), apiKey, uri, bodyHash)
|
||||
|
||||
return fmt.Sprintf("%s;%s;%s;%02x", login, timestamp, c.saltShaker(), sha1.Sum([]byte(hashInput)))
|
||||
}
|
||||
|
||||
func getRandomSalt() []byte {
|
||||
// This is the only part of this that needs to be serialized.
|
||||
salt := make([]byte, 16)
|
||||
for i := 0; i < 16; i++ {
|
||||
salt[i] = saltBytes[rand.Intn(len(saltBytes))]
|
||||
}
|
||||
|
||||
return salt
|
||||
}
|
||||
|
@ -9,7 +9,9 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@ -24,6 +26,9 @@ func setupTest(t *testing.T) (*Client, *http.ServeMux) {
|
||||
client.HTTPClient = server.Client()
|
||||
client.baseURL, _ = url.Parse(server.URL)
|
||||
|
||||
client.signer.saltShaker = func() []byte { return []byte("0123456789ABCDEF") }
|
||||
client.signer.clock = func() time.Time { return time.Unix(1692475113, 0) }
|
||||
|
||||
return client, mux
|
||||
}
|
||||
|
||||
@ -147,3 +152,63 @@ func TestClient_RemoveRecord_error(t *testing.T) {
|
||||
err := client.RemoveRecord(context.Background(), "example.com", record)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestSigner_Sign(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
path string
|
||||
now int64
|
||||
salt string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
desc: "basic",
|
||||
path: "/path",
|
||||
now: 1692475113,
|
||||
salt: "0123456789ABCDEF",
|
||||
expected: "user;1692475113;0123456789ABCDEF;417a9988c7ad7919b297884dd120b5808d8a1e6f",
|
||||
},
|
||||
{
|
||||
desc: "another date",
|
||||
path: "/path",
|
||||
now: 1692567766,
|
||||
salt: "0123456789ABCDEF",
|
||||
expected: "user;1692567766;0123456789ABCDEF;b5c28286fd2e1a45a7c576dc2a6430116f721502",
|
||||
},
|
||||
{
|
||||
desc: "another salt",
|
||||
path: "/path",
|
||||
now: 1692475113,
|
||||
salt: "FEDCBA9876543210",
|
||||
expected: "user;1692475113;FEDCBA9876543210;0f766822bda4fdc09829be4e1ea5e27ae3ae334e",
|
||||
},
|
||||
{
|
||||
desc: "empty path",
|
||||
path: "",
|
||||
now: 1692475113,
|
||||
salt: "0123456789ABCDEF",
|
||||
expected: "user;1692475113;0123456789ABCDEF;c7c241a4d15d04d92805631d58d4d72ac1c339a1",
|
||||
},
|
||||
{
|
||||
desc: "root path",
|
||||
path: "/",
|
||||
now: 1692475113,
|
||||
salt: "0123456789ABCDEF",
|
||||
expected: "user;1692475113;0123456789ABCDEF;c7c241a4d15d04d92805631d58d4d72ac1c339a1",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
signer := NewSigner()
|
||||
signer.saltShaker = func() []byte { return []byte(test.salt) }
|
||||
signer.clock = func() time.Time { return time.Unix(test.now, 0) }
|
||||
|
||||
sign := signer.Sign(test.path, "data", "user", "secret")
|
||||
|
||||
assert.Equal(t, test.expected, sign)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user