File: sign.go

package info (click to toggle)
golang-github-openpubkey-openpubkey 0.18.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 964 kB
  • sloc: makefile: 10
file content (263 lines) | stat: -rw-r--r-- 6,941 bytes parent folder | download | duplicates (2)
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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
// Copyright 2024 OpenPubkey
//
// 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
//
//     http://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.
//
// SPDX-License-Identifier: Apache-2.0

package gq

import (
	"crypto/rand"
	"encoding/json"
	"fmt"
	"math/big"

	"filippo.io/bigmod"
	"github.com/awnumar/memguard"
	"github.com/lestrrat-go/jwx/v2/jws"
	"github.com/openpubkey/openpubkey/util"
)

// Sign creates a GQ1 signature over the given message with the given GQ1 private number.
//
// Comments throughout refer to stages as specified in the ISO/IEC 14888-2 standard.
func (sv *signerVerifier) Sign(private []byte, message []byte) ([]byte, error) {
	n, v, t := sv.n, sv.v, sv.t
	vBytes := sv.vBytes

	M := message
	Q, err := bigmod.NewNat().SetBytes(private, n)
	if err != nil {
		return nil, err
	}

	// Stage 1 - select t numbers, each consisting of nBytes random bytes.
	// In order to guarantee our operation is constant time, we deviate slightly
	// from the standard and directly select an integer less than n
	r, err := randomNumbers(t, sv.n)
	if err != nil {
		return nil, err
	}

	// Stage 2 - calculate test number W
	// for i from 1 to t, compute W_i <- r_i^v mod n
	// combine to form W
	var W []byte
	for i := 0; i < t; i++ {
		W_i := bigmod.NewNat().Exp(r[i], v.Bytes(), n)
		W = append(W, W_i.Bytes(n)...)
	}

	// Stage 3 - calculate question number R
	// hash W and M and take first t*vBytes bytes as R
	R, err := hash(t*vBytes, W, M)
	if err != nil {
		return nil, err
	}

	// split R into t numbers each consisting of vBytes bytes
	Rs := make([]*bigmod.Nat, t)
	for i := 0; i < t; i++ {
		Rs[i], err = new(bigmod.Nat).SetBytes(R[i*vBytes:(i+1)*vBytes], n)
		if err != nil {
			return nil, err
		}
	}

	// Stage 4 - calculate witness number S
	// for i from 1 to t, compute S_i <- r_i * Q^{R_i} mod n
	// combine to form S
	var S []byte
	for i := 0; i < t; i++ {
		S_i := bigmod.NewNat().Exp(Q, Rs[i].Bytes(n), n)
		S_i.Mul(r[i], n)
		S = append(S, S_i.Bytes(n)...)
	}

	// proof is combination of R and S
	return encodeProof(R, S), nil
}

func (sv *signerVerifier) SignJWT(jwt []byte, opts ...Opts) ([]byte, error) {
	options := &OptsStruct{}
	for _, applyOpt := range opts {
		applyOpt(options)
	}
	// Ensure that someone doesn't use a reserved protected header claim name
	for _, reserved := range []string{"alg", "typ", "kid"} {
		if _, ok := options.extraClaims[reserved]; ok {
			return nil, fmt.Errorf("use of reserved header name, %s, in additional headers", reserved)
		}
	}

	origHeaders, payload, signature, err := jws.SplitCompact(jwt)
	if err != nil {
		return nil, err
	}

	signingPayload := util.JoinJWTSegments(origHeaders, payload)

	headers := jws.NewHeaders()
	err = headers.Set(jws.AlgorithmKey, GQ256)
	if err != nil {
		return nil, err
	}
	err = headers.Set(jws.TypeKey, "JWT")
	if err != nil {
		return nil, err
	}
	err = headers.Set(jws.KeyIDKey, string(origHeaders))
	if err != nil {
		return nil, err
	}

	for k, v := range options.extraClaims {
		if err = headers.Set(k, v); err != nil {
			return nil, err
		}
	}

	headersJSON, err := json.Marshal(headers)
	if err != nil {
		return nil, err
	}

	headersEnc := util.Base64EncodeForJWT(headersJSON)

	// When jwt is parsed it's split into base64-encoded bytes, but
	// we need the raw signature to calculate mod inverse
	decodedSig, err := util.Base64DecodeForJWT(signature)
	if err != nil {
		return nil, err
	}

	// GQ1 private number (Q) is inverse of RSA signature mod n
	private, err := sv.modInverse(memguard.NewBufferFromBytes(decodedSig))
	if err != nil {
		return nil, err
	}

	defer private.Destroy()

	gqSig, err := sv.Sign(private.Bytes(), signingPayload)
	if err != nil {
		return nil, err
	}

	// Now make a new GQ-signed token
	gqToken := util.JoinJWTSegments(headersEnc, payload, gqSig)

	return gqToken, nil
}

// modInverse finds the modular multiplicative inverse of the value stored in b
//
// All operations involving the secret value are performed either with constant-
// time methods or with blinding (if sv has a source of randomness)
func (sv *signerVerifier) modInverse(b *memguard.LockedBuffer) (*memguard.LockedBuffer, error) {
	x, err := bigmod.NewNat().SetBytes(b.Bytes(), sv.n)
	if err != nil {
		return nil, err
	}

	nInt := natAsInt(sv.n.Nat(), sv.n)
	var r *big.Int
	var rConstant, xr *bigmod.Nat

	// Apply RSA blinding to the ModInverse operation.
	// Translates the technique formerly used in the Go Standard Library before they
	// switched to bigmod in late 2022. Since bigmod does not yet support constant-time
	// ModInverse, we perform the blinding so that the value of the private key is not
	// detectable via side channel.
	// Ref: https://github.com/golang/go/blob/5f60f844beb0581a19cb425a3338d79d322a7db2/src/crypto/rsa/rsa.go#L567-L596
	//
	// For a secret value x, the idea is to find m = 1/x mod n by calculating
	// rm/r mod n ==> r/(xr) mod n, where r is a random value

	for {
		// draw r
		r, err = rand.Int(rand.Reader, nInt)
		if err != nil {
			return nil, err
		}

		// compute xr = x * r
		xr, err = intAsNat(r, sv.n)
		if err != nil {
			return nil, err
		}
		xr.Mul(x, sv.n)

		// check that xr has a multiplicative inverse mod n. It is exceedingly
		// rare but technically possible for it not to, in which case we need
		// to draw a new value for r
		xrInt := natAsInt(xr, sv.n)
		inverse := new(big.Int).ModInverse(xrInt, nInt)
		if inverse != nil {
			break
		}
	}

	// overwrite x with the blinded value
	x = xr

	// calculate m/r mod n
	m := natAsInt(x, sv.n).ModInverse(natAsInt(x, sv.n), nInt)
	mConstant, err := intAsNat(m, sv.n)
	if err != nil {
		return nil, err
	}

	// remove the blinding by multiplying m/r by r
	rConstant, err = intAsNat(r, sv.n)
	if err != nil {
		return nil, err
	}
	mConstant.Mul(rConstant, sv.n)

	mFinal := natAsInt(mConstant, sv.n)

	// need to allocate memory for fixed length slice using FillBytes
	ret := make([]byte, len(b.Bytes()))
	defer b.Destroy()

	return memguard.NewBufferFromBytes(mFinal.FillBytes(ret)), nil
}

func encodeProof(R, S []byte) []byte {
	var bin []byte

	bin = append(bin, R...)
	bin = append(bin, S...)

	return util.Base64EncodeForJWT(bin)
}

var randomNumbers = func(t int, n *bigmod.Modulus) ([]*bigmod.Nat, error) {
	nInt := modAsInt(n)
	ys := make([]*bigmod.Nat, t)

	for i := 0; i < t; i++ {
		r, err := rand.Int(rand.Reader, nInt)
		if err != nil {
			return nil, err
		}

		ys[i], err = intAsNat(r, n)
		if err != nil {
			return nil, err
		}
	}

	return ys, nil
}