|
| 1 | +// Copyright © 2019-2024 Binance |
| 2 | +// |
| 3 | +// This file is part of Binance. The full Binance copyright notice, including |
| 4 | +// terms governing use, modification, and redistribution, is contained in the |
| 5 | +// file LICENSE at the root of the source code distribution tree. |
| 6 | + |
| 7 | +// Package common provides constant-time big integer operations for cryptographic use. |
| 8 | +// |
| 9 | +// SECURITY NOTE: Go's math/big package is NOT constant-time and should not be used |
| 10 | +// with secret values. This module provides constant-time alternatives using |
| 11 | +// filippo.io/bigmod, which is the same library used by Go's crypto/rsa. |
| 12 | +// |
| 13 | +// Reference: https://github.com/golang/go/issues/20654 |
| 14 | + |
| 15 | +package common |
| 16 | + |
| 17 | +import ( |
| 18 | + "crypto/rand" |
| 19 | + "crypto/subtle" |
| 20 | + "math/big" |
| 21 | + "sync" |
| 22 | + "sync/atomic" |
| 23 | + "time" |
| 24 | + |
| 25 | + "filippo.io/bigmod" |
| 26 | +) |
| 27 | + |
| 28 | +// constantTimeEnabled controls whether constant-time operations are used. |
| 29 | +// Default is false (disabled) for performance. Enable for high-security environments. |
| 30 | +var constantTimeEnabled int32 = 0 |
| 31 | + |
| 32 | +// EnableConstantTimeOps enables constant-time cryptographic operations. |
| 33 | +// Call this at application startup if timing side-channel protection is required. |
| 34 | +func EnableConstantTimeOps() { |
| 35 | + atomic.StoreInt32(&constantTimeEnabled, 1) |
| 36 | +} |
| 37 | + |
| 38 | +// DisableConstantTimeOps disables constant-time operations (default). |
| 39 | +func DisableConstantTimeOps() { |
| 40 | + atomic.StoreInt32(&constantTimeEnabled, 0) |
| 41 | +} |
| 42 | + |
| 43 | +// IsConstantTimeEnabled returns true if constant-time operations are enabled. |
| 44 | +func IsConstantTimeEnabled() bool { |
| 45 | + return atomic.LoadInt32(&constantTimeEnabled) == 1 |
| 46 | +} |
| 47 | + |
| 48 | +// CTModInt provides constant-time modular arithmetic using filippo.io/bigmod. |
| 49 | +// This is the recommended implementation as bigmod is: |
| 50 | +// 1. Maintained by the Go crypto team lead (Filippo Valsorda) |
| 51 | +// 2. The same code used internally by crypto/rsa and crypto/ecdsa |
| 52 | +// 3. Highly optimized with architecture-specific assembly |
| 53 | +type CTModInt struct { |
| 54 | + mod *bigmod.Modulus |
| 55 | + modBigInt *big.Int |
| 56 | + inverseExp []byte // Exponent for modular inverse: p-2 (prime) or phi(n)-1 (composite) |
| 57 | + byteLen int |
| 58 | + bytePool sync.Pool |
| 59 | +} |
| 60 | + |
| 61 | +// NewCTModInt creates a constant-time modular context using bigmod. |
| 62 | +// Note: bigmod requires odd modulus for Exp operations. |
| 63 | +func NewCTModInt(mod *big.Int) *CTModInt { |
| 64 | + modBytes := mod.Bytes() |
| 65 | + m, err := bigmod.NewModulus(modBytes) |
| 66 | + if err != nil { |
| 67 | + // Fallback: should not happen for valid modulus |
| 68 | + panic(err) |
| 69 | + } |
| 70 | + |
| 71 | + // Pre-compute mod-2 for Fermat inverse: a^(-1) = a^(mod-2) mod mod |
| 72 | + modMinusTwo := new(big.Int).Sub(mod, big.NewInt(2)) |
| 73 | + |
| 74 | + byteLen := len(modBytes) |
| 75 | + return &CTModInt{ |
| 76 | + mod: m, |
| 77 | + modBigInt: new(big.Int).Set(mod), |
| 78 | + inverseExp: modMinusTwo.Bytes(), |
| 79 | + byteLen: byteLen, |
| 80 | + bytePool: sync.Pool{ |
| 81 | + New: func() interface{} { |
| 82 | + return make([]byte, byteLen) |
| 83 | + }, |
| 84 | + }, |
| 85 | + } |
| 86 | +} |
| 87 | + |
| 88 | +// reduceToPaddedBytes reduces val mod ct.modBigInt and returns a zero-padded |
| 89 | +// byte slice of length ct.byteLen suitable for bigmod.Nat.SetBytes. |
| 90 | +// The reduction uses big.Int.Mod which is safe here because the modulus is public. |
| 91 | +func (ct *CTModInt) reduceToPaddedBytes(val *big.Int) []byte { |
| 92 | + reduced := val |
| 93 | + if val.Sign() < 0 || val.Cmp(ct.modBigInt) >= 0 { |
| 94 | + reduced = new(big.Int).Mod(val, ct.modBigInt) |
| 95 | + } |
| 96 | + |
| 97 | + buf := ct.bytePool.Get().([]byte) |
| 98 | + for i := range buf { |
| 99 | + buf[i] = 0 |
| 100 | + } |
| 101 | + b := reduced.Bytes() |
| 102 | + copy(buf[ct.byteLen-len(b):], b) |
| 103 | + return buf |
| 104 | +} |
| 105 | + |
| 106 | +// ExpCT performs constant-time modular exponentiation using bigmod. |
| 107 | +// IMPORTANT: The modulus must be odd. Negative exponents are not supported and will panic. |
| 108 | +func (ct *CTModInt) ExpCT(base, exp *big.Int) *big.Int { |
| 109 | + if exp.Sign() == 0 { |
| 110 | + return big.NewInt(1) |
| 111 | + } |
| 112 | + if exp.Sign() < 0 { |
| 113 | + panic("ExpCT: negative exponents are not supported; use ModInverseCT explicitly") |
| 114 | + } |
| 115 | + |
| 116 | + paddedBase := ct.reduceToPaddedBytes(base) |
| 117 | + defer func() { |
| 118 | + for i := range paddedBase { |
| 119 | + paddedBase[i] = 0 |
| 120 | + } |
| 121 | + ct.bytePool.Put(paddedBase) |
| 122 | + }() |
| 123 | + |
| 124 | + baseNat := bigmod.NewNat() |
| 125 | + baseNat.SetBytes(paddedBase, ct.mod) |
| 126 | + |
| 127 | + expBytes := exp.Bytes() |
| 128 | + result := bigmod.NewNat() |
| 129 | + result.Exp(baseNat, expBytes, ct.mod) |
| 130 | + |
| 131 | + return new(big.Int).SetBytes(result.Bytes(ct.mod)) |
| 132 | +} |
| 133 | + |
| 134 | +// ModInverseCT computes the modular inverse in constant time using Fermat's little theorem. |
| 135 | +// For a prime modulus p: a^(-1) = a^(p-2) mod p |
| 136 | +// For a non-prime modulus n with known phi(n): a^(-1) = a^(phi(n)-1) mod n |
| 137 | +// SECURITY: This uses constant-time Exp, making the entire operation constant-time. |
| 138 | +// Note: The modulus should be prime for this to work correctly. For composite moduli, |
| 139 | +// use NewCTModIntWithPhi to provide phi(n). |
| 140 | +func (ct *CTModInt) ModInverseCT(a *big.Int) *big.Int { |
| 141 | + if a.Sign() == 0 { |
| 142 | + return nil |
| 143 | + } |
| 144 | + |
| 145 | + paddedA := ct.reduceToPaddedBytes(a) |
| 146 | + defer func() { |
| 147 | + for i := range paddedA { |
| 148 | + paddedA[i] = 0 |
| 149 | + } |
| 150 | + ct.bytePool.Put(paddedA) |
| 151 | + }() |
| 152 | + |
| 153 | + aNat := bigmod.NewNat() |
| 154 | + aNat.SetBytes(paddedA, ct.mod) |
| 155 | + |
| 156 | + result := bigmod.NewNat() |
| 157 | + result.Exp(aNat, ct.inverseExp, ct.mod) |
| 158 | + |
| 159 | + return new(big.Int).SetBytes(result.Bytes(ct.mod)) |
| 160 | +} |
| 161 | + |
| 162 | +// Mod returns the modulus as a big.Int. |
| 163 | +func (ct *CTModInt) Mod() *big.Int { |
| 164 | + return new(big.Int).Set(ct.modBigInt) |
| 165 | +} |
| 166 | + |
| 167 | +// MulCT performs constant-time modular multiplication using bigmod. |
| 168 | +func (ct *CTModInt) MulCT(x, y *big.Int) *big.Int { |
| 169 | + paddedX := ct.reduceToPaddedBytes(x) |
| 170 | + paddedY := ct.reduceToPaddedBytes(y) |
| 171 | + defer func() { |
| 172 | + for i := range paddedX { |
| 173 | + paddedX[i] = 0 |
| 174 | + } |
| 175 | + ct.bytePool.Put(paddedX) |
| 176 | + }() |
| 177 | + defer func() { |
| 178 | + for i := range paddedY { |
| 179 | + paddedY[i] = 0 |
| 180 | + } |
| 181 | + ct.bytePool.Put(paddedY) |
| 182 | + }() |
| 183 | + |
| 184 | + xNat := bigmod.NewNat() |
| 185 | + yNat := bigmod.NewNat() |
| 186 | + xNat.SetBytes(paddedX, ct.mod) |
| 187 | + yNat.SetBytes(paddedY, ct.mod) |
| 188 | + |
| 189 | + xNat.Mul(yNat, ct.mod) |
| 190 | + |
| 191 | + return new(big.Int).SetBytes(xNat.Bytes(ct.mod)) |
| 192 | +} |
| 193 | + |
| 194 | +// NewCTModIntWithPhi creates a constant-time modular context for composite moduli. |
| 195 | +// This is required for correct ModInverse on composite moduli where phi(n) is known. |
| 196 | +// For RSA-like moduli n = p*q, pass phiN = (p-1)*(q-1). |
| 197 | +func NewCTModIntWithPhi(mod, phiN *big.Int) *CTModInt { |
| 198 | + modBytes := mod.Bytes() |
| 199 | + m, err := bigmod.NewModulus(modBytes) |
| 200 | + if err != nil { |
| 201 | + panic(err) |
| 202 | + } |
| 203 | + |
| 204 | + // For composite modulus: a^(-1) = a^(phi(n)-1) mod n |
| 205 | + phiMinusOne := new(big.Int).Sub(phiN, big.NewInt(1)) |
| 206 | + |
| 207 | + byteLen := len(modBytes) |
| 208 | + return &CTModInt{ |
| 209 | + mod: m, |
| 210 | + modBigInt: new(big.Int).Set(mod), |
| 211 | + inverseExp: phiMinusOne.Bytes(), // Use phi(n)-1 instead of n-2 |
| 212 | + byteLen: byteLen, |
| 213 | + bytePool: sync.Pool{ |
| 214 | + New: func() interface{} { |
| 215 | + return make([]byte, byteLen) |
| 216 | + }, |
| 217 | + }, |
| 218 | + } |
| 219 | +} |
| 220 | + |
| 221 | +// TimingProtection provides response time normalization to prevent timing attacks. |
| 222 | +type TimingProtection struct { |
| 223 | + targetDuration time.Duration |
| 224 | + jitterRange time.Duration |
| 225 | +} |
| 226 | + |
| 227 | +// NewTimingProtection creates a TimingProtection with custom parameters. |
| 228 | +// targetDuration is the minimum padded duration for every operation. |
| 229 | +// jitterRange adds a random delay on top of targetDuration to prevent fingerprinting |
| 230 | +// the fixed padding boundary. |
| 231 | +func NewTimingProtection(targetDuration, jitterRange time.Duration) *TimingProtection { |
| 232 | + return &TimingProtection{ |
| 233 | + targetDuration: targetDuration, |
| 234 | + jitterRange: jitterRange, |
| 235 | + } |
| 236 | +} |
| 237 | + |
| 238 | +// ProtectBigInt wraps a function that returns *big.Int with timing normalization. |
| 239 | +// The total execution time is always >= targetDuration + a random jitter, regardless |
| 240 | +// of how long the actual operation takes. |
| 241 | +func (tp *TimingProtection) ProtectBigInt(fn func() (*big.Int, error)) (*big.Int, error) { |
| 242 | + startTime := time.Now() |
| 243 | + result, err := fn() |
| 244 | + elapsed := time.Since(startTime) |
| 245 | + |
| 246 | + padTo := tp.targetDuration |
| 247 | + if elapsed > padTo { |
| 248 | + padTo = elapsed |
| 249 | + } |
| 250 | + if tp.jitterRange > 0 { |
| 251 | + jitterNanos, _ := rand.Int(rand.Reader, big.NewInt(int64(tp.jitterRange))) |
| 252 | + padTo += time.Duration(jitterNanos.Int64()) |
| 253 | + } |
| 254 | + if remaining := padTo - elapsed; remaining > 0 { |
| 255 | + time.Sleep(remaining) |
| 256 | + } |
| 257 | + return result, err |
| 258 | +} |
| 259 | + |
| 260 | +// ConstantTimeCompare compares two big.Int values in constant time. |
| 261 | +// Both values are padded to padLen bytes before comparison to avoid leaking |
| 262 | +// relative magnitude. If padLen is 0, the maximum of the two byte lengths is used. |
| 263 | +func ConstantTimeCompare(a, b *big.Int, padLen int) int { |
| 264 | + aBytes := a.Bytes() |
| 265 | + bBytes := b.Bytes() |
| 266 | + |
| 267 | + if padLen <= 0 { |
| 268 | + padLen = len(aBytes) |
| 269 | + if len(bBytes) > padLen { |
| 270 | + padLen = len(bBytes) |
| 271 | + } |
| 272 | + } |
| 273 | + |
| 274 | + padA := make([]byte, padLen) |
| 275 | + padB := make([]byte, padLen) |
| 276 | + if len(aBytes) <= padLen { |
| 277 | + copy(padA[padLen-len(aBytes):], aBytes) |
| 278 | + } else { |
| 279 | + copy(padA, aBytes[len(aBytes)-padLen:]) |
| 280 | + } |
| 281 | + if len(bBytes) <= padLen { |
| 282 | + copy(padB[padLen-len(bBytes):], bBytes) |
| 283 | + } else { |
| 284 | + copy(padB, bBytes[len(bBytes)-padLen:]) |
| 285 | + } |
| 286 | + |
| 287 | + return subtle.ConstantTimeCompare(padA, padB) |
| 288 | +} |
0 commit comments