1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
|
package srp
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/binary"
"errors"
"time"
"golang.org/x/crypto/argon2"
"golang.org/x/crypto/curve25519"
)
// Implementation following the "context" package
var DeadlineExceeded error = deadlineExceededError{}
type deadlineExceededError struct{}
func (deadlineExceededError) Error() string {
return "srp: deadline exceeded calculating proof-of-work challenge"
}
func (deadlineExceededError) Timeout() bool { return true }
func (deadlineExceededError) Temporary() bool { return true }
const ecdlpPRFKeySize = 32
func unixMilli(currentTime time.Time) int64 {
return currentTime.UnixNano() / 1e6
}
// ECDLPChallenge computes the base64 solution for a given ECDLP base64 challenge
// within deadlineUnixMilli milliseconds, if any was found. Deadlines are measured on the
// wall clock, not the monotonic clock, due to unreliability on mobile devices.
// deadlineUnixMilli = -1 means unlimited time.
func ECDLPChallenge(b64Challenge string, deadlineUnixMilli int64) (b64Solution string, err error) {
challenge, err := base64.StdEncoding.DecodeString(b64Challenge)
if err != nil {
return "", err
}
if len(challenge) != 2*ecdlpPRFKeySize+sha256.Size {
return "", errors.New("srp: invalid ECDLP challenge length")
}
var i uint64
var point []byte
buffer := make([]byte, 8)
for i = 0; ; i++ {
if deadlineUnixMilli >= 0 && unixMilli(time.Now()) > int64(deadlineUnixMilli) {
return "", DeadlineExceeded
}
prePRF := hmac.New(sha256.New, challenge[:ecdlpPRFKeySize])
binary.LittleEndian.PutUint64(buffer, i)
_, _ = prePRF.Write(buffer)
point, err = curve25519.X25519(prePRF.Sum(nil), curve25519.Basepoint)
if err != nil {
return "", err
}
postPRF := hmac.New(sha256.New, challenge[ecdlpPRFKeySize:2*ecdlpPRFKeySize])
_, _ = postPRF.Write(point)
if bytes.Equal(postPRF.Sum(nil), challenge[2*ecdlpPRFKeySize:]) {
break
}
}
solution := []byte{}
solution = append(solution, buffer...)
solution = append(solution, point...)
return base64.StdEncoding.EncodeToString(solution), nil
}
const argon2PRFKeySize = 32
// Argon2PreimageChallenge computes the base64 solution for a given Argon2 base64
// challenge within deadlineUnixMilli milliseconds, if any was found. Deadlines are measured
// on the wall clock, not the monotonic clock, due to unreliability on mobile devices.
// deadlineUnixMilli = -1 means unlimited time.
func Argon2PreimageChallenge(b64Challenge string, deadlineUnixMilli int64) (b64Solution string, err error) {
challenge, err := base64.StdEncoding.DecodeString(b64Challenge)
if err != nil {
return "", err
}
// Argon2 challenges consist of 3 PRF keys, the hash output, and 4 32-bit argon2 parameters
if len(challenge) != 3*argon2PRFKeySize+sha256.Size+4*4 {
return "", errors.New("srp: invalid Argon2 preimage challenge length")
}
prfKeys := challenge[:3*argon2PRFKeySize]
goal := challenge[3*argon2PRFKeySize:][:sha256.Size]
argon2Params := challenge[3*argon2PRFKeySize+sha256.Size:]
threads := binary.LittleEndian.Uint32(argon2Params[0:])
argon2OutputSize := binary.LittleEndian.Uint32(argon2Params[4:])
memoryCost := binary.LittleEndian.Uint32(argon2Params[8:])
timeCost := binary.LittleEndian.Uint32(argon2Params[12:])
var i uint64
var stage2 []byte
buffer := make([]byte, 8)
for i = 0; ; i++ {
if deadlineUnixMilli >= 0 && unixMilli(time.Now()) > int64(deadlineUnixMilli) {
return "", DeadlineExceeded
}
prePRF := hmac.New(sha256.New, prfKeys[:argon2PRFKeySize])
binary.LittleEndian.PutUint64(buffer, i)
_, _ = prePRF.Write(buffer)
stage2 = argon2.IDKey(prePRF.Sum(nil), prfKeys[argon2PRFKeySize:2*argon2PRFKeySize], timeCost, memoryCost, uint8(threads), argon2OutputSize)
postPRF := hmac.New(sha256.New, prfKeys[2*argon2PRFKeySize:])
_, _ = postPRF.Write(stage2)
if bytes.Equal(postPRF.Sum(nil), goal) {
break
}
}
solution := []byte{}
solution = append(solution, buffer...)
solution = append(solution, stage2...)
return base64.StdEncoding.EncodeToString(solution), nil
}
|