1*e7b1675dSTing-Kang Chang// Copyright 2020 Google LLC 2*e7b1675dSTing-Kang Chang// 3*e7b1675dSTing-Kang Chang// Licensed under the Apache License, Version 2.0 (the "License"); 4*e7b1675dSTing-Kang Chang// you may not use this file except in compliance with the License. 5*e7b1675dSTing-Kang Chang// You may obtain a copy of the License at 6*e7b1675dSTing-Kang Chang// 7*e7b1675dSTing-Kang Chang// http://www.apache.org/licenses/LICENSE-2.0 8*e7b1675dSTing-Kang Chang// 9*e7b1675dSTing-Kang Chang// Unless required by applicable law or agreed to in writing, software 10*e7b1675dSTing-Kang Chang// distributed under the License is distributed on an "AS IS" BASIS, 11*e7b1675dSTing-Kang Chang// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12*e7b1675dSTing-Kang Chang// See the License for the specific language governing permissions and 13*e7b1675dSTing-Kang Chang// limitations under the License. 14*e7b1675dSTing-Kang Chang// 15*e7b1675dSTing-Kang Chang//////////////////////////////////////////////////////////////////////////////// 16*e7b1675dSTing-Kang Chang 17*e7b1675dSTing-Kang Changpackage subtle 18*e7b1675dSTing-Kang Chang 19*e7b1675dSTing-Kang Changimport ( 20*e7b1675dSTing-Kang Chang "bytes" 21*e7b1675dSTing-Kang Chang "crypto/elliptic" 22*e7b1675dSTing-Kang Chang "crypto/rand" 23*e7b1675dSTing-Kang Chang "errors" 24*e7b1675dSTing-Kang Chang "fmt" 25*e7b1675dSTing-Kang Chang "math/big" 26*e7b1675dSTing-Kang Chang) 27*e7b1675dSTing-Kang Chang 28*e7b1675dSTing-Kang Chang// ECPublicKey represents a elliptic curve public key. 29*e7b1675dSTing-Kang Changtype ECPublicKey struct { 30*e7b1675dSTing-Kang Chang elliptic.Curve 31*e7b1675dSTing-Kang Chang Point ECPoint 32*e7b1675dSTing-Kang Chang} 33*e7b1675dSTing-Kang Chang 34*e7b1675dSTing-Kang Chang// ECPrivateKey represents a elliptic curve private key. 35*e7b1675dSTing-Kang Changtype ECPrivateKey struct { 36*e7b1675dSTing-Kang Chang PublicKey ECPublicKey 37*e7b1675dSTing-Kang Chang D *big.Int 38*e7b1675dSTing-Kang Chang} 39*e7b1675dSTing-Kang Chang 40*e7b1675dSTing-Kang Chang// GetECPrivateKey converts a stored private key to ECPrivateKey. 41*e7b1675dSTing-Kang Changfunc GetECPrivateKey(c elliptic.Curve, b []byte) *ECPrivateKey { 42*e7b1675dSTing-Kang Chang d := new(big.Int) 43*e7b1675dSTing-Kang Chang d.SetBytes(b) 44*e7b1675dSTing-Kang Chang 45*e7b1675dSTing-Kang Chang x, y := c.Params().ScalarBaseMult(b) 46*e7b1675dSTing-Kang Chang pub := ECPublicKey{ 47*e7b1675dSTing-Kang Chang Curve: c, 48*e7b1675dSTing-Kang Chang Point: ECPoint{ 49*e7b1675dSTing-Kang Chang X: x, 50*e7b1675dSTing-Kang Chang Y: y, 51*e7b1675dSTing-Kang Chang }, 52*e7b1675dSTing-Kang Chang } 53*e7b1675dSTing-Kang Chang return &ECPrivateKey{ 54*e7b1675dSTing-Kang Chang PublicKey: pub, 55*e7b1675dSTing-Kang Chang D: d, 56*e7b1675dSTing-Kang Chang } 57*e7b1675dSTing-Kang Chang 58*e7b1675dSTing-Kang Chang} 59*e7b1675dSTing-Kang Chang 60*e7b1675dSTing-Kang Chang// ECPoint represents a point on the elliptic curve. 61*e7b1675dSTing-Kang Changtype ECPoint struct { 62*e7b1675dSTing-Kang Chang X, Y *big.Int 63*e7b1675dSTing-Kang Chang} 64*e7b1675dSTing-Kang Chang 65*e7b1675dSTing-Kang Changfunc (p *ECPrivateKey) getParams() *elliptic.CurveParams { 66*e7b1675dSTing-Kang Chang return p.PublicKey.Curve.Params() 67*e7b1675dSTing-Kang Chang} 68*e7b1675dSTing-Kang Chang 69*e7b1675dSTing-Kang Changfunc getModulus(c elliptic.Curve) *big.Int { 70*e7b1675dSTing-Kang Chang return c.Params().P 71*e7b1675dSTing-Kang Chang} 72*e7b1675dSTing-Kang Chang 73*e7b1675dSTing-Kang Changfunc fieldSizeInBits(c elliptic.Curve) int { 74*e7b1675dSTing-Kang Chang t := big.NewInt(1) 75*e7b1675dSTing-Kang Chang r := t.Sub(getModulus(c), t) 76*e7b1675dSTing-Kang Chang return r.BitLen() 77*e7b1675dSTing-Kang Chang} 78*e7b1675dSTing-Kang Chang 79*e7b1675dSTing-Kang Changfunc fieldSizeInBytes(c elliptic.Curve) int { 80*e7b1675dSTing-Kang Chang return (fieldSizeInBits(c) + 7) / 8 81*e7b1675dSTing-Kang Chang} 82*e7b1675dSTing-Kang Chang 83*e7b1675dSTing-Kang Changfunc encodingSizeInBytes(c elliptic.Curve, p string) (int, error) { 84*e7b1675dSTing-Kang Chang cSize := fieldSizeInBytes(c) 85*e7b1675dSTing-Kang Chang switch p { 86*e7b1675dSTing-Kang Chang case "UNCOMPRESSED": 87*e7b1675dSTing-Kang Chang return 2*cSize + 1, nil 88*e7b1675dSTing-Kang Chang case "DO_NOT_USE_CRUNCHY_UNCOMPRESSED": 89*e7b1675dSTing-Kang Chang return 2 * cSize, nil 90*e7b1675dSTing-Kang Chang case "COMPRESSED": 91*e7b1675dSTing-Kang Chang return cSize + 1, nil 92*e7b1675dSTing-Kang Chang } 93*e7b1675dSTing-Kang Chang return 0, fmt.Errorf("invalid point format :%s", p) 94*e7b1675dSTing-Kang Chang 95*e7b1675dSTing-Kang Chang} 96*e7b1675dSTing-Kang Chang 97*e7b1675dSTing-Kang Chang// PointEncode encodes a point into the format specified. 98*e7b1675dSTing-Kang Changfunc PointEncode(c elliptic.Curve, pFormat string, pt ECPoint) ([]byte, error) { 99*e7b1675dSTing-Kang Chang if !c.IsOnCurve(pt.X, pt.Y) { 100*e7b1675dSTing-Kang Chang return nil, errors.New("curve check failed") 101*e7b1675dSTing-Kang Chang } 102*e7b1675dSTing-Kang Chang cSize := fieldSizeInBytes(c) 103*e7b1675dSTing-Kang Chang y := pt.Y.Bytes() 104*e7b1675dSTing-Kang Chang x := pt.X.Bytes() 105*e7b1675dSTing-Kang Chang switch pFormat { 106*e7b1675dSTing-Kang Chang case "UNCOMPRESSED": 107*e7b1675dSTing-Kang Chang encoded := make([]byte, 2*cSize+1) 108*e7b1675dSTing-Kang Chang copy(encoded[1+2*cSize-len(y):], y) 109*e7b1675dSTing-Kang Chang copy(encoded[1+cSize-len(x):], x) 110*e7b1675dSTing-Kang Chang encoded[0] = 4 111*e7b1675dSTing-Kang Chang return encoded, nil 112*e7b1675dSTing-Kang Chang case "DO_NOT_USE_CRUNCHY_UNCOMPRESSED": 113*e7b1675dSTing-Kang Chang encoded := make([]byte, 2*cSize) 114*e7b1675dSTing-Kang Chang if len(x) > cSize { 115*e7b1675dSTing-Kang Chang x = bytes.Replace(x, []byte("\x00"), []byte{}, -1) 116*e7b1675dSTing-Kang Chang } 117*e7b1675dSTing-Kang Chang if len(y) > cSize { 118*e7b1675dSTing-Kang Chang y = bytes.Replace(y, []byte("\x00"), []byte{}, -1) 119*e7b1675dSTing-Kang Chang } 120*e7b1675dSTing-Kang Chang copy(encoded[2*cSize-len(y):], y) 121*e7b1675dSTing-Kang Chang copy(encoded[cSize-len(x):], x) 122*e7b1675dSTing-Kang Chang return encoded, nil 123*e7b1675dSTing-Kang Chang case "COMPRESSED": 124*e7b1675dSTing-Kang Chang encoded := make([]byte, cSize+1) 125*e7b1675dSTing-Kang Chang copy(encoded[1+cSize-len(x):], x) 126*e7b1675dSTing-Kang Chang encoded[0] = 2 127*e7b1675dSTing-Kang Chang if pt.Y.Bit(0) > 0 { 128*e7b1675dSTing-Kang Chang encoded[0] = 3 129*e7b1675dSTing-Kang Chang } 130*e7b1675dSTing-Kang Chang return encoded, nil 131*e7b1675dSTing-Kang Chang } 132*e7b1675dSTing-Kang Chang return nil, errors.New("invalid point format") 133*e7b1675dSTing-Kang Chang 134*e7b1675dSTing-Kang Chang} 135*e7b1675dSTing-Kang Chang 136*e7b1675dSTing-Kang Chang// PointDecode decodes a encoded point to return an ECPoint 137*e7b1675dSTing-Kang Changfunc PointDecode(c elliptic.Curve, pFormat string, e []byte) (*ECPoint, error) { 138*e7b1675dSTing-Kang Chang cSize := fieldSizeInBytes(c) 139*e7b1675dSTing-Kang Chang x, y := new(big.Int), new(big.Int) 140*e7b1675dSTing-Kang Chang switch pFormat { 141*e7b1675dSTing-Kang Chang case "UNCOMPRESSED": 142*e7b1675dSTing-Kang Chang if len(e) != (2*cSize + 1) { 143*e7b1675dSTing-Kang Chang return nil, errors.New("invalid point size") 144*e7b1675dSTing-Kang Chang } 145*e7b1675dSTing-Kang Chang if e[0] != 4 { 146*e7b1675dSTing-Kang Chang return nil, errors.New("invalid point format") 147*e7b1675dSTing-Kang Chang } 148*e7b1675dSTing-Kang Chang x.SetBytes(e[1 : cSize+1]) 149*e7b1675dSTing-Kang Chang y.SetBytes(e[cSize+1:]) 150*e7b1675dSTing-Kang Chang if !c.IsOnCurve(x, y) { 151*e7b1675dSTing-Kang Chang return nil, errors.New("invalid point") 152*e7b1675dSTing-Kang Chang } 153*e7b1675dSTing-Kang Chang return &ECPoint{ 154*e7b1675dSTing-Kang Chang X: x, 155*e7b1675dSTing-Kang Chang Y: y, 156*e7b1675dSTing-Kang Chang }, nil 157*e7b1675dSTing-Kang Chang case "DO_NOT_USE_CRUNCHY_UNCOMPRESSED": 158*e7b1675dSTing-Kang Chang if len(e) != 2*cSize { 159*e7b1675dSTing-Kang Chang return nil, errors.New("invalid point size") 160*e7b1675dSTing-Kang Chang } 161*e7b1675dSTing-Kang Chang x.SetBytes(e[:cSize]) 162*e7b1675dSTing-Kang Chang y.SetBytes(e[cSize:]) 163*e7b1675dSTing-Kang Chang if !c.IsOnCurve(x, y) { 164*e7b1675dSTing-Kang Chang return nil, errors.New("invalid point") 165*e7b1675dSTing-Kang Chang } 166*e7b1675dSTing-Kang Chang return &ECPoint{ 167*e7b1675dSTing-Kang Chang X: x, 168*e7b1675dSTing-Kang Chang Y: y, 169*e7b1675dSTing-Kang Chang }, nil 170*e7b1675dSTing-Kang Chang case "COMPRESSED": 171*e7b1675dSTing-Kang Chang if len(e) != cSize+1 { 172*e7b1675dSTing-Kang Chang return nil, errors.New("compressed point has wrong length") 173*e7b1675dSTing-Kang Chang } 174*e7b1675dSTing-Kang Chang lsb := false 175*e7b1675dSTing-Kang Chang if e[0] == 2 { 176*e7b1675dSTing-Kang Chang lsb = false 177*e7b1675dSTing-Kang Chang } else if e[0] == 3 { 178*e7b1675dSTing-Kang Chang lsb = true 179*e7b1675dSTing-Kang Chang } else { 180*e7b1675dSTing-Kang Chang return nil, errors.New("invalid format") 181*e7b1675dSTing-Kang Chang } 182*e7b1675dSTing-Kang Chang x := new(big.Int) 183*e7b1675dSTing-Kang Chang x.SetBytes(e[1:]) 184*e7b1675dSTing-Kang Chang if (x.Sign() == -1) || (x.Cmp(c.Params().P) != -1) { 185*e7b1675dSTing-Kang Chang return nil, errors.New("x is out of range") 186*e7b1675dSTing-Kang Chang } 187*e7b1675dSTing-Kang Chang y := getY(x, lsb, c) 188*e7b1675dSTing-Kang Chang return &ECPoint{ 189*e7b1675dSTing-Kang Chang X: x, 190*e7b1675dSTing-Kang Chang Y: y, 191*e7b1675dSTing-Kang Chang }, nil 192*e7b1675dSTing-Kang Chang } 193*e7b1675dSTing-Kang Chang return nil, fmt.Errorf("invalid format: %s", pFormat) 194*e7b1675dSTing-Kang Chang} 195*e7b1675dSTing-Kang Chang 196*e7b1675dSTing-Kang Changfunc getY(x *big.Int, lsb bool, c elliptic.Curve) *big.Int { 197*e7b1675dSTing-Kang Chang // y² = x³ - 3x + b 198*e7b1675dSTing-Kang Chang x3 := new(big.Int).Mul(x, x) 199*e7b1675dSTing-Kang Chang x3.Mul(x3, x) 200*e7b1675dSTing-Kang Chang 201*e7b1675dSTing-Kang Chang threeX := new(big.Int).Lsh(x, 1) 202*e7b1675dSTing-Kang Chang threeX.Add(threeX, x) 203*e7b1675dSTing-Kang Chang b := c.Params().B 204*e7b1675dSTing-Kang Chang p := c.Params().P 205*e7b1675dSTing-Kang Chang 206*e7b1675dSTing-Kang Chang x3.Sub(x3, threeX) 207*e7b1675dSTing-Kang Chang x3.Add(x3, b) 208*e7b1675dSTing-Kang Chang x3.ModSqrt(x3, p) 209*e7b1675dSTing-Kang Chang e := uint(1) 210*e7b1675dSTing-Kang Chang if lsb { 211*e7b1675dSTing-Kang Chang e = 0 212*e7b1675dSTing-Kang Chang } 213*e7b1675dSTing-Kang Chang if e == x3.Bit(0) { 214*e7b1675dSTing-Kang Chang x3 := x3.Sub(p, x3) 215*e7b1675dSTing-Kang Chang x3.Mod(x3, p) 216*e7b1675dSTing-Kang Chang } 217*e7b1675dSTing-Kang Chang return x3 218*e7b1675dSTing-Kang Chang} 219*e7b1675dSTing-Kang Chang 220*e7b1675dSTing-Kang Changfunc validatePublicPoint(pub *ECPoint, priv *ECPrivateKey) error { 221*e7b1675dSTing-Kang Chang if priv.PublicKey.Curve.IsOnCurve(pub.X, pub.Y) { 222*e7b1675dSTing-Kang Chang return nil 223*e7b1675dSTing-Kang Chang } 224*e7b1675dSTing-Kang Chang return errors.New("invalid public key") 225*e7b1675dSTing-Kang Chang} 226*e7b1675dSTing-Kang Chang 227*e7b1675dSTing-Kang Chang// ComputeSharedSecret is used to compute a shared secret using given private key and peer public key. 228*e7b1675dSTing-Kang Changfunc ComputeSharedSecret(pub *ECPoint, priv *ECPrivateKey) ([]byte, error) { 229*e7b1675dSTing-Kang Chang if err := validatePublicPoint(pub, priv); err != nil { 230*e7b1675dSTing-Kang Chang return nil, err 231*e7b1675dSTing-Kang Chang } 232*e7b1675dSTing-Kang Chang 233*e7b1675dSTing-Kang Chang x, y := priv.PublicKey.Curve.ScalarMult(pub.X, pub.Y, priv.D.Bytes()) 234*e7b1675dSTing-Kang Chang 235*e7b1675dSTing-Kang Chang if x == nil { 236*e7b1675dSTing-Kang Chang return nil, errors.New("shared key compute error") 237*e7b1675dSTing-Kang Chang } 238*e7b1675dSTing-Kang Chang // check if x,y are on the curve 239*e7b1675dSTing-Kang Chang if err := validatePublicPoint(&ECPoint{X: x, Y: y}, priv); err != nil { 240*e7b1675dSTing-Kang Chang return nil, errors.New("invalid shared key") 241*e7b1675dSTing-Kang Chang } 242*e7b1675dSTing-Kang Chang 243*e7b1675dSTing-Kang Chang sharedSecret := make([]byte, maxSharedKeyLength(priv.PublicKey)) 244*e7b1675dSTing-Kang Chang return x.FillBytes(sharedSecret), nil 245*e7b1675dSTing-Kang Chang} 246*e7b1675dSTing-Kang Chang 247*e7b1675dSTing-Kang Changfunc maxSharedKeyLength(pub ECPublicKey) int { 248*e7b1675dSTing-Kang Chang return (pub.Curve.Params().BitSize + 7) / 8 249*e7b1675dSTing-Kang Chang} 250*e7b1675dSTing-Kang Chang 251*e7b1675dSTing-Kang Chang// GenerateECDHKeyPair will create a new private key for a given curve. 252*e7b1675dSTing-Kang Changfunc GenerateECDHKeyPair(c elliptic.Curve) (*ECPrivateKey, error) { 253*e7b1675dSTing-Kang Chang p, x, y, err := elliptic.GenerateKey(c, rand.Reader) 254*e7b1675dSTing-Kang Chang if err != nil { 255*e7b1675dSTing-Kang Chang return nil, err 256*e7b1675dSTing-Kang Chang } 257*e7b1675dSTing-Kang Chang return &ECPrivateKey{ 258*e7b1675dSTing-Kang Chang PublicKey: ECPublicKey{ 259*e7b1675dSTing-Kang Chang Curve: c, 260*e7b1675dSTing-Kang Chang Point: ECPoint{ 261*e7b1675dSTing-Kang Chang X: x, 262*e7b1675dSTing-Kang Chang Y: y, 263*e7b1675dSTing-Kang Chang }, 264*e7b1675dSTing-Kang Chang }, 265*e7b1675dSTing-Kang Chang D: new(big.Int).SetBytes(p), 266*e7b1675dSTing-Kang Chang }, nil 267*e7b1675dSTing-Kang Chang 268*e7b1675dSTing-Kang Chang} 269*e7b1675dSTing-Kang Chang 270*e7b1675dSTing-Kang Chang// GetCurve returns the elliptic.Curve for a given standard curve name. 271*e7b1675dSTing-Kang Changfunc GetCurve(c string) (elliptic.Curve, error) { 272*e7b1675dSTing-Kang Chang switch c { 273*e7b1675dSTing-Kang Chang case "secp224r1", "NIST_P224", "P-224": 274*e7b1675dSTing-Kang Chang return elliptic.P224(), nil 275*e7b1675dSTing-Kang Chang case "secp256r1", "NIST_P256", "P-256", "EllipticCurveType_NIST_P256": 276*e7b1675dSTing-Kang Chang return elliptic.P256(), nil 277*e7b1675dSTing-Kang Chang case "secp384r1", "NIST_P384", "P-384", "EllipticCurveType_NIST_P384": 278*e7b1675dSTing-Kang Chang return elliptic.P384(), nil 279*e7b1675dSTing-Kang Chang case "secp521r1", "NIST_P521", "P-521", "EllipticCurveType_NIST_P521": 280*e7b1675dSTing-Kang Chang return elliptic.P521(), nil 281*e7b1675dSTing-Kang Chang default: 282*e7b1675dSTing-Kang Chang return nil, errors.New("unsupported curve") 283*e7b1675dSTing-Kang Chang } 284*e7b1675dSTing-Kang Chang} 285