go-ssb-room/internal/signinwithssb/challenges.go

79 lines
2.1 KiB
Go

// SPDX-FileCopyrightText: 2021 The NGI Pointer Secure-Scuttlebutt Team of 2020/2021
//
// SPDX-License-Identifier: MIT
package signinwithssb
import (
"bytes"
"crypto/rand"
"encoding/base64"
"fmt"
"golang.org/x/crypto/ed25519"
refs "github.com/ssbc/go-ssb-refs"
)
// sign-in with ssb uses 256-bit nonces
const challengeLength = 32
// DecodeChallengeString accepts base64 encoded strings and decodes them,
// checks their length to be equal to challengeLength,
// and returns the decoded bytes
func DecodeChallengeString(c string) ([]byte, error) {
challengeBytes, err := base64.URLEncoding.DecodeString(c)
if err != nil {
return nil, fmt.Errorf("invalid challenge encoding: %w", err)
}
if n := len(challengeBytes); n != challengeLength {
return nil, fmt.Errorf("invalid challenge length: expected %d but got %d", challengeLength, n)
}
return challengeBytes, nil
}
// GenerateChallenge returs a base64 encoded string
// with challangeLength bytes of random data
func GenerateChallenge() string {
buf := make([]byte, challengeLength)
rand.Read(buf)
return base64.URLEncoding.EncodeToString(buf)
}
// ClientPayload is used to create and verify solutions
type ClientPayload struct {
ClientID, ServerID refs.FeedRef
ClientChallenge string
ServerChallenge string
}
// recreate the signed message
func (cr ClientPayload) createMessage() []byte {
var msg bytes.Buffer
msg.WriteString("=http-auth-sign-in:")
msg.WriteString(cr.ServerID.String())
msg.WriteString(":")
msg.WriteString(cr.ClientID.String())
msg.WriteString(":")
msg.WriteString(cr.ServerChallenge)
msg.WriteString(":")
msg.WriteString(cr.ClientChallenge)
return msg.Bytes()
}
// Sign returns the signature created with the passed privateKey
func (cr ClientPayload) Sign(privateKey ed25519.PrivateKey) []byte {
msg := cr.createMessage()
return ed25519.Sign(privateKey, msg)
}
// Validate checks the signature by calling createMessage() and ed25519.Verify()
// together with the ClientID public key.
func (cr ClientPayload) Validate(signature []byte) bool {
msg := cr.createMessage()
return ed25519.Verify(cr.ClientID.PubKey(), msg, signature)
}