11package script
22
33import (
4+ "bytes"
5+ "crypto"
6+ "crypto/ecdsa"
7+ "crypto/elliptic"
8+ "crypto/rsa"
9+ "fmt"
410 "os"
511 "path/filepath"
12+ "strconv"
13+ "strings"
614 "testing"
715 "time"
816
@@ -44,7 +52,6 @@ func TestCryptoJWTCommand(t *testing.T) {
4452 testscript .Run (t , testscript.Params {
4553 Files : []string {"testdata/crypto/jwt.txtar" },
4654 Setup : func (e * testscript.Env ) error {
47-
4855 err := os .WriteFile (filepath .Join (e .Cd , "p256.pem" ), b , 0600 )
4956 require .NoError (t , err )
5057 err = os .WriteFile (filepath .Join (e .Cd , "token.txt" ), []byte (raw ), 0600 )
@@ -55,8 +62,121 @@ func TestCryptoJWTCommand(t *testing.T) {
5562 })
5663}
5764
65+ func TestCryptoKeyPair (t * testing.T ) {
66+ testscript .Run (t , testscript.Params {
67+ Files : []string {"testdata/crypto/keypair.txtar" },
68+ Cmds : map [string ]func (ts * testscript.TestScript , neg bool , args []string ){
69+ "check_key_pair" : checkKeyPair ,
70+ },
71+ })
72+ }
73+
5874func TestCryptoHelp (t * testing.T ) {
5975 testscript .Run (t , testscript.Params {
6076 Files : []string {"testdata/crypto/help.txtar" },
6177 })
6278}
79+
80+ func keyLength (jwk * jose.JSONWebKey ) (int , error ) {
81+ switch key := jwk .Key .(type ) {
82+ case []byte :
83+ return len (key ) * 8 , nil
84+ case * rsa.PrivateKey :
85+ return key .N .BitLen (), nil
86+ case * rsa.PublicKey :
87+ return key .N .BitLen (), nil
88+ case * ecdsa.PrivateKey :
89+ return key .Params ().BitSize , nil
90+ case * ecdsa.PublicKey :
91+ return key .Params ().BitSize , nil
92+ default :
93+ return 0 , fmt .Errorf ("unsupported key type: %T" , key )
94+ }
95+ }
96+
97+ func keyCurve (jwk * jose.JSONWebKey ) (elliptic.Curve , error ) {
98+ switch key := jwk .Key .(type ) {
99+ case * ecdsa.PrivateKey :
100+ return key .Curve , nil
101+ case * ecdsa.PublicKey :
102+ return key .Curve , nil
103+ default :
104+ return nil , fmt .Errorf ("unsupported key type: %T" , key )
105+ }
106+ }
107+
108+ // checkKeyPair checks that the public/private key pair is valid. It performs
109+ // the following checks:
110+ //
111+ // - Read and parse the JWK public key, validating it's a valid public key
112+ // - Read and parse the JWK private key, validating it's a valid private key
113+ // - Compare the public and private key SHA-1 thumbprints to verify they match
114+ // - The type of the key that was created
115+ // - For RSA keys, the key size is the expected size
116+ // - For EC keys, the key curve is the expected curve
117+ func checkKeyPair (ts * testscript.TestScript , neg bool , args []string ) {
118+ if len (args ) < 4 {
119+ ts .Fatalf ("expected at least 4 arguments, got %d" , len (args ))
120+ }
121+
122+ pub , err := jose .ParseKey ([]byte (ts .ReadFile (args [0 ])))
123+ ts .Check (err )
124+ priv , err := jose .ParseKey ([]byte (ts .ReadFile (args [1 ])), jose .WithPassword ([]byte ("password" )))
125+ ts .Check (err )
126+
127+ pubHash , err := pub .Thumbprint (crypto .SHA1 )
128+ ts .Check (err )
129+ privHash , err := priv .Thumbprint (crypto .SHA1 )
130+ ts .Check (err )
131+
132+ if ! bytes .Equal (pubHash , privHash ) {
133+ ts .Fatalf ("%s and %s have different thumbprints" , args [0 ], args [1 ])
134+ }
135+
136+ expectRSA := false
137+ if s := strings .ToUpper (args [2 ]); s == "RSA" {
138+ expectRSA = true
139+ }
140+
141+ if expectRSA {
142+ if ! strings .HasPrefix (pub .Algorithm , "RS" ) {
143+ ts .Fatalf ("expected RSA key type, got %q" , pub .Algorithm )
144+ }
145+
146+ expectedLength , err := strconv .Atoi (args [3 ])
147+ ts .Check (err )
148+
149+ length , err := keyLength (pub )
150+ ts .Check (err )
151+
152+ if length != expectedLength {
153+ ts .Fatalf ("key length mismatch: expected %d, got %d" , expectedLength , length )
154+ }
155+
156+ return
157+ }
158+
159+ if ! strings .HasPrefix (pub .Algorithm , "ES" ) {
160+ ts .Fatalf ("expected EC key type, got %q" , pub .Algorithm )
161+ }
162+
163+ kc , err := keyCurve (pub )
164+ ts .Check (err )
165+
166+ switch crv := strings .ToUpper (args [3 ]); crv {
167+ case "P-256" :
168+ if kc != elliptic .P256 () {
169+ ts .Fatalf ("expected P-256 curve, got %q" , kc )
170+ }
171+ case "P-384" :
172+ if kc != elliptic .P384 () {
173+ ts .Fatalf ("expected P-384 curve, got %q" , kc )
174+ }
175+ case "P-521" :
176+ if kc != elliptic .P521 () {
177+ ts .Fatalf ("expected P-521 curve, got %q" , kc )
178+ }
179+ default :
180+ ts .Fatalf ("unknown curve %q" , crv )
181+ }
182+ }
0 commit comments