| // Copyright 2025 The BoringSSL Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| // Package spake2plus implements RFC 9383 for testing. |
| package spake2plus |
| |
| import ( |
| "bytes" |
| "crypto/elliptic" |
| "crypto/hkdf" |
| "crypto/hmac" |
| "crypto/rand" |
| "crypto/sha256" |
| "encoding/binary" |
| "errors" |
| "io" |
| "math/big" |
| |
| "golang.org/x/crypto/scrypt" |
| ) |
| |
| const ( |
| verifierSize = 32 // size of w0, w1 in bytes, for P-256 |
| registrationRecordSize = 65 // uncompressed P-256 point size |
| shareSize = 65 |
| confirmSize = 32 |
| keySize = 32 |
| pbkdfOutputSize = 80 |
| ) |
| |
| type Role int |
| |
| const ( |
| RoleProver Role = iota |
| RoleVerifier |
| ) |
| |
| type state int |
| |
| const ( |
| stateInit state = iota |
| stateShareGenerated |
| stateKeyGenerated |
| ) |
| |
| type Context struct { |
| IDProver []byte |
| IDVerifier []byte |
| Role Role |
| |
| curve elliptic.Curve |
| context []byte |
| w0 *big.Int |
| w1 *big.Int |
| Lx, Ly *big.Int // L point |
| Mx, My *big.Int // M point |
| Nx, Ny *big.Int // N point |
| Xx, Xy *big.Int // X point |
| Yx, Yy *big.Int // Y point |
| Zx, Zy *big.Int // Z point |
| Vx, Vy *big.Int // V point |
| x *big.Int // ephemeral scalar for prover |
| y *big.Int // ephemeral scalar for verifier |
| share []byte |
| confirm []byte |
| state state |
| } |
| |
| // Hardcoded M and N from the RFC (uncompressed) |
| var kM = []byte{ |
| 0x04, 0x88, 0x6e, 0x2f, 0x97, 0xac, 0xe4, 0x6e, 0x55, 0xba, 0x9d, |
| 0xd7, 0x24, 0x25, 0x79, 0xf2, 0x99, 0x3b, 0x64, 0xe1, 0x6e, 0xf3, |
| 0xdc, 0xab, 0x95, 0xaf, 0xd4, 0x97, 0x33, 0x3d, 0x8f, 0xa1, 0x2f, |
| 0x5f, 0xf3, 0x55, 0x16, 0x3e, 0x43, 0xce, 0x22, 0x4e, 0x0b, 0x0e, |
| 0x65, 0xff, 0x02, 0xac, 0x8e, 0x5c, 0x7b, 0xe0, 0x94, 0x19, 0xc7, |
| 0x85, 0xe0, 0xca, 0x54, 0x7d, 0x55, 0xa1, 0x2e, 0x2d, 0x20, |
| } |
| |
| var kN = []byte{ |
| 0x04, 0xd8, 0xbb, 0xd6, 0xc6, 0x39, 0xc6, 0x29, 0x37, 0xb0, 0x4d, |
| 0x99, 0x7f, 0x38, 0xc3, 0x77, 0x07, 0x19, 0xc6, 0x29, 0xd7, 0x01, |
| 0x4d, 0x49, 0xa2, 0x4b, 0x4f, 0x98, 0xba, 0xa1, 0x29, 0x2b, 0x49, |
| 0x07, 0xd6, 0x0a, 0xa6, 0xbf, 0xad, 0xe4, 0x50, 0x08, 0xa6, 0x36, |
| 0x33, 0x7f, 0x51, 0x68, 0xc6, 0x4d, 0x9b, 0xd3, 0x60, 0x34, 0x80, |
| 0x8c, 0xd5, 0x64, 0x49, 0x0b, 0x1e, 0x65, 0x6e, 0xdb, 0xe7, |
| } |
| |
| func Register( |
| pw []byte, |
| idProver []byte, |
| idVerifier []byte, |
| ) (pwVerifierW0 []byte, pwVerifierW1 []byte, registrationRecord []byte, err error) { |
| mhfBuf := new(bytes.Buffer) |
| if err := binary.Write(mhfBuf, binary.LittleEndian, uint64(len(pw))); err != nil { |
| return nil, nil, nil, err |
| } |
| mhfBuf.Write(pw) |
| if err := binary.Write(mhfBuf, binary.LittleEndian, uint64(len(idProver))); err != nil { |
| return nil, nil, nil, err |
| } |
| mhfBuf.Write(idProver) |
| if err := binary.Write(mhfBuf, binary.LittleEndian, uint64(len(idVerifier))); err != nil { |
| return nil, nil, nil, err |
| } |
| mhfBuf.Write(idVerifier) |
| |
| key, err := scrypt.Key(mhfBuf.Bytes(), nil, 32768, 8, 1, pbkdfOutputSize) |
| if err != nil { |
| return nil, nil, nil, err |
| } |
| |
| curve := elliptic.P256() |
| N := curve.Params().N |
| |
| w0 := new(big.Int).SetBytes(key[:pbkdfOutputSize/2]) |
| w0.Mod(w0, N) |
| |
| w1 := new(big.Int).SetBytes(key[pbkdfOutputSize/2:]) |
| w1.Mod(w1, N) |
| |
| pwVerifierW0 = make([]byte, verifierSize) |
| pwVerifierW1 = make([]byte, verifierSize) |
| copy(pwVerifierW0, w0.Bytes()) |
| copy(pwVerifierW1, w1.Bytes()) |
| |
| Lx, Ly := curve.ScalarBaseMult(w1.Bytes()) |
| L := elliptic.Marshal(curve, Lx, Ly) |
| registrationRecord = make([]byte, registrationRecordSize) |
| copy(registrationRecord, L) |
| |
| return pwVerifierW0, pwVerifierW1, registrationRecord, nil |
| } |
| |
| func newContext( |
| role Role, |
| context []byte, |
| idProver []byte, |
| idVerifier []byte, |
| pwVerifierW0 []byte, |
| pwVerifierW1 []byte, |
| registrationRecord []byte, |
| x *big.Int, |
| y *big.Int, |
| ) (*Context, error) { |
| curve := elliptic.P256() |
| |
| Mx, My := elliptic.Unmarshal(curve, kM) |
| if Mx == nil { |
| return nil, errors.New("invalid M point") |
| } |
| Nx, Ny := elliptic.Unmarshal(curve, kN) |
| if Nx == nil { |
| return nil, errors.New("invalid N point") |
| } |
| |
| ctx := &Context{ |
| Role: role, |
| curve: curve, |
| context: append([]byte(nil), context...), |
| IDProver: append([]byte(nil), idProver...), |
| IDVerifier: append([]byte(nil), idVerifier...), |
| Mx: Mx, |
| My: My, |
| Nx: Nx, |
| Ny: Ny, |
| state: stateInit, |
| } |
| |
| N := curve.Params().N |
| |
| if role == RoleProver { |
| if pwVerifierW0 == nil || pwVerifierW1 == nil || y != nil { |
| return nil, errors.New("invalid parameters for prover") |
| } |
| |
| ctx.w0 = new(big.Int).SetBytes(pwVerifierW0) |
| ctx.w1 = new(big.Int).SetBytes(pwVerifierW1) |
| ctx.w0.Mod(ctx.w0, N) |
| ctx.w1.Mod(ctx.w1, N) |
| |
| if x == nil { |
| xRand, err := randFieldElement(curve) |
| if err != nil { |
| return nil, err |
| } |
| ctx.x = xRand |
| } else { |
| ctx.x = new(big.Int).Set(x) |
| } |
| } else { |
| // Verifier |
| if pwVerifierW0 == nil || registrationRecord == nil || x != nil { |
| return nil, errors.New("invalid parameters for verifier") |
| } |
| |
| ctx.w0 = new(big.Int).SetBytes(pwVerifierW0) |
| ctx.w0.Mod(ctx.w0, N) |
| |
| // Load L |
| Lx, Ly := elliptic.Unmarshal(curve, registrationRecord) |
| if Lx == nil { |
| return nil, errors.New("invalid L point") |
| } |
| ctx.Lx, ctx.Ly = Lx, Ly |
| |
| if y == nil { |
| yRand, err := randFieldElement(curve) |
| if err != nil { |
| return nil, err |
| } |
| ctx.y = yRand |
| } else { |
| ctx.y = new(big.Int).Set(y) |
| } |
| } |
| |
| return ctx, nil |
| } |
| |
| func randFieldElement(curve elliptic.Curve) (*big.Int, error) { |
| params := curve.Params() |
| b := make([]byte, (params.BitSize+7)/8) |
| var k *big.Int |
| for { |
| if _, err := rand.Read(b); err != nil { |
| return nil, err |
| } |
| k = new(big.Int).SetBytes(b) |
| if k.Sign() != 0 && k.Cmp(params.N) < 0 { |
| break |
| } |
| } |
| return k, nil |
| } |
| |
| func NewProver( |
| context []byte, idProver []byte, idVerifier []byte, |
| pwVerifierW0 []byte, pwVerifierW1 []byte, |
| ) (*Context, error) { |
| return newContext(RoleProver, context, idProver, idVerifier, |
| pwVerifierW0, pwVerifierW1, nil, nil, nil) |
| } |
| |
| func NewVerifier( |
| context []byte, idProver []byte, idVerifier []byte, |
| pwVerifierW0 []byte, registrationRecord []byte, |
| ) (*Context, error) { |
| return newContext(RoleVerifier, context, idProver, idVerifier, |
| pwVerifierW0, nil, registrationRecord, nil, nil) |
| } |
| |
| func (ctx *Context) GenerateProverShare() (share []byte, err error) { |
| if ctx.Role != RoleProver { |
| return nil, errors.New("invalid state for prover share generation") |
| } |
| if ctx.state != stateInit { |
| return ctx.share, nil |
| } |
| curve := ctx.curve |
| |
| // l = x * G |
| lx, ly := curve.ScalarBaseMult(ctx.x.Bytes()) |
| // r = w0 * M |
| rx, ry := curve.ScalarMult(ctx.Mx, ctx.My, ctx.w0.Bytes()) |
| // X = l + r |
| Xx, Xy := curve.Add(lx, ly, rx, ry) |
| ctx.Xx, ctx.Xy = Xx, Xy |
| |
| share = elliptic.Marshal(curve, Xx, Xy) |
| ctx.share = append([]byte(nil), share...) |
| ctx.state = stateShareGenerated |
| return share, nil |
| } |
| |
| func updateWithLengthPrefix(h io.Writer, data []byte) { |
| var lenLe [8]byte |
| binary.LittleEndian.PutUint64(lenLe[:], uint64(len(data))) |
| h.Write(lenLe[:]) |
| h.Write(data) |
| } |
| |
| func computeTranscriptAndConfirmation( |
| ctx *Context, |
| shareP, shareV []byte, |
| ) (proverConfirm, verifierConfirm, sharedSecret []byte, err error) { |
| curve := ctx.curve |
| Z := elliptic.Marshal(curve, ctx.Zx, ctx.Zy) |
| V := elliptic.Marshal(curve, ctx.Vx, ctx.Vy) |
| |
| h := sha256.New() |
| updateWithLengthPrefix(h, ctx.context) |
| updateWithLengthPrefix(h, ctx.IDProver) |
| updateWithLengthPrefix(h, ctx.IDVerifier) |
| updateWithLengthPrefix(h, kM) |
| updateWithLengthPrefix(h, kN) |
| updateWithLengthPrefix(h, shareP) |
| updateWithLengthPrefix(h, shareV) |
| updateWithLengthPrefix(h, Z) |
| updateWithLengthPrefix(h, V) |
| updateWithLengthPrefix(h, ctx.w0.Bytes()) |
| K_main := h.Sum(nil) |
| |
| confirmationStr := []byte("ConfirmationKeys") |
| keys := doHKDF(K_main, confirmationStr, keySize*2) |
| secretInfoStr := []byte("SharedKey") |
| sharedSecret = doHKDF(K_main, secretInfoStr, keySize) |
| |
| // Prover confirmation = HMAC(keys[:32], shareV) |
| macP := hmac.New(sha256.New, keys[:keySize]) |
| macP.Write(shareV) |
| proverConfirm = macP.Sum(nil) |
| |
| // Verifier confirmation = HMAC(keys[32:], shareP) |
| macV := hmac.New(sha256.New, keys[keySize:]) |
| macV.Write(shareP) |
| verifierConfirm = macV.Sum(nil) |
| |
| return |
| } |
| |
| func doHKDF(ikm, info []byte, size int) []byte { |
| out, err := hkdf.Key(sha256.New, ikm, nil, string(info), size) |
| if err != nil { |
| panic(err) |
| } |
| return out |
| } |
| |
| func (ctx *Context) ProcessProverShare( |
| proverShare []byte, |
| ) (verifierShare []byte, verifierConfirm []byte, sharedSecret []byte, err error) { |
| if ctx.Role != RoleVerifier || ctx.state != stateInit || len(proverShare) != shareSize { |
| return nil, nil, nil, errors.New("invalid state or share") |
| } |
| curve := ctx.curve |
| |
| // Y = y*G + w0*N |
| lx, ly := curve.ScalarBaseMult(ctx.y.Bytes()) |
| rx, ry := curve.ScalarMult(ctx.Nx, ctx.Ny, ctx.w0.Bytes()) |
| Yx, Yy := curve.Add(lx, ly, rx, ry) |
| ctx.Yx, ctx.Yy = Yx, Yy |
| |
| verifierShare = elliptic.Marshal(curve, Yx, Yy) |
| if px, py := elliptic.Unmarshal(curve, proverShare); px == nil { |
| return nil, nil, nil, errors.New("invalid prover share") |
| } else { |
| ctx.Xx, ctx.Xy = px, py |
| } |
| |
| // T = X - w0*M |
| mx, my := curve.ScalarMult(ctx.Mx, ctx.My, ctx.w0.Bytes()) |
| mx, my = mx, new(big.Int).Neg(my) |
| my.Mod(my, curve.Params().P) |
| |
| Tx, Ty := curve.Add(ctx.Xx, ctx.Xy, mx, my) |
| // Z = (y)*T |
| Zx, Zy := curve.ScalarMult(Tx, Ty, ctx.y.Bytes()) |
| ctx.Zx, ctx.Zy = Zx, Zy |
| // V = (y)*L |
| Vx, Vy := curve.ScalarMult(ctx.Lx, ctx.Ly, ctx.y.Bytes()) |
| ctx.Vx, ctx.Vy = Vx, Vy |
| |
| proverConfirm, verifierConfirm, sharedSecret, err := computeTranscriptAndConfirmation(ctx, proverShare, verifierShare) |
| if err != nil { |
| return nil, nil, nil, err |
| } |
| |
| ctx.confirm = proverConfirm |
| ctx.state = stateKeyGenerated |
| |
| return verifierShare, verifierConfirm, sharedSecret, nil |
| } |
| |
| // computeProverConfirmation (Prover side) |
| func (ctx *Context) ComputeProverConfirmation( |
| verifierShare []byte, |
| claimedVerifierConfirm []byte, |
| ) (proverConfirm []byte, sharedSecret []byte, err error) { |
| if ctx.Role != RoleProver || ctx.state != stateShareGenerated || len(verifierShare) != shareSize || len(claimedVerifierConfirm) != confirmSize { |
| return nil, nil, errors.New("invalid state or input") |
| } |
| curve := ctx.curve |
| vx, vy := elliptic.Unmarshal(curve, verifierShare) |
| if vx == nil { |
| return nil, nil, errors.New("invalid verifier share") |
| } |
| ctx.Yx, ctx.Yy = vx, vy |
| |
| // T = Y - w0*N |
| nx, ny := curve.ScalarMult(ctx.Nx, ctx.Ny, ctx.w0.Bytes()) |
| ny.Neg(ny) |
| ny.Mod(ny, curve.Params().P) |
| |
| Tx, Ty := curve.Add(ctx.Yx, ctx.Yy, nx, ny) |
| |
| // Z = x*T |
| Zx, Zy := curve.ScalarMult(Tx, Ty, ctx.x.Bytes()) |
| ctx.Zx, ctx.Zy = Zx, Zy |
| |
| // V = w1*T |
| Vx, Vy := curve.ScalarMult(Tx, Ty, ctx.w1.Bytes()) |
| ctx.Vx, ctx.Vy = Vx, Vy |
| |
| // Compute transcript |
| proverConfirm, verifierConfirm, sharedSecret, err := computeTranscriptAndConfirmation(ctx, ctx.share, verifierShare) |
| if err != nil { |
| return nil, nil, err |
| } |
| |
| // Check verifier confirm |
| if !hmac.Equal(verifierConfirm, claimedVerifierConfirm) { |
| return nil, nil, errors.New("verifier confirmation mismatch") |
| } |
| |
| ctx.state = stateKeyGenerated |
| |
| return proverConfirm, sharedSecret, nil |
| } |
| |
| // VerifyProverConfirmation (Verifier side) |
| func (ctx *Context) VerifyProverConfirmation(proverConfirm []byte) error { |
| if ctx.Role != RoleVerifier || ctx.state != stateKeyGenerated || len(proverConfirm) != confirmSize { |
| return errors.New("invalid state or input") |
| } |
| if !hmac.Equal(ctx.confirm, proverConfirm) { |
| return errors.New("prover confirmation mismatch") |
| } |
| return nil |
| } |