github.com/google/osv-scalibr@v0.4.1/veles/secrets/gcpsak/signature.go (about) 1 // Copyright 2025 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package gcpsak 16 17 import ( 18 "crypto" 19 "crypto/rsa" 20 "crypto/x509" 21 "encoding/pem" 22 "errors" 23 "fmt" 24 ) 25 26 const ( 27 // hashAlgo is the cryptographic hash function used to hash the payload before 28 // signing it. 29 // We chose SHA256, because that is what's used in the JWT / OAuth2 flow that 30 // GCP SAK are ultimately for. 31 // See https://cs.opensource.google/go/x/oauth2/+/refs/tags/v0.20.0:jws/jws.go;l=156 for details. 32 hashAlgo = crypto.SHA256 33 34 // payload is used for every signature. 35 // We chose a known static payload, so we don't accidentally generate 36 // something that could be used for a real authentication flow on an attack on 37 // it. 38 payload = "Don't leak keys, pretty please!" 39 ) 40 41 var ( 42 // payloadHash contains the (constant) hash of the payload. 43 // Since we're using a static payload, we can compute its hash ahead of time. 44 payloadHash []byte 45 ) 46 47 func init() { //nolint:gochecknoinits 48 h := hashAlgo.New() 49 if _, err := h.Write([]byte(payload)); err != nil { 50 // Guaranteed to never return an error. 51 panic(err) 52 } 53 payloadHash = h.Sum(nil) 54 } 55 56 // Sign uses the privateKey (PEM format) to sign the static payload. 57 // This allows us to validate a GCP SAK downstream without having to hold on to 58 // (and thus potentially leak) its private key. 59 // 60 // It uses the same flow that the JWT / OAuth2 flow uses for GCP SAK, so we 61 // don't accidentally expose the key to cross-algorithm attacks. It uses SHA256 62 // and SignPKCS1v15. 63 // For details, see https://cs.opensource.google/go/x/oauth2/+/refs/tags/v0.20.0:jws/jws.go;l=156. 64 // 65 // Returns nil if signing was not possible; i.e. because the key was not 66 // well-formed. 67 func Sign(privateKey string) []byte { 68 block, _ := pem.Decode([]byte(privateKey)) 69 if block == nil || block.Type != "PRIVATE KEY" { 70 return nil 71 } 72 key, err := x509.ParsePKCS8PrivateKey(block.Bytes) 73 if err != nil { 74 return nil 75 } 76 priv, ok := key.(*rsa.PrivateKey) 77 if !ok { 78 return nil 79 } 80 sig, err := rsa.SignPKCS1v15(nil, priv, hashAlgo, payloadHash) 81 if err != nil { 82 return nil 83 } 84 return sig 85 } 86 87 // Valid checks whether sig was signed using the private key contained in the 88 // certificate cert (PEM format). 89 // 90 // Returns true if the signature was successfully validated; false otherwise. 91 // Returns error if parsing the certificate or extracting the public key failed. 92 func Valid(sig []byte, cert string) (bool, error) { 93 block, _ := pem.Decode([]byte(cert)) 94 if block == nil || block.Type != "CERTIFICATE" { 95 return false, errors.New("no valid PEM block of type CERTIFICATE") 96 } 97 crt, err := x509.ParseCertificate(block.Bytes) 98 if err != nil { 99 return false, fmt.Errorf("unable to parse certificate: %w", err) 100 } 101 pub, ok := crt.PublicKey.(*rsa.PublicKey) 102 if !ok { 103 return false, fmt.Errorf("public key was %T not RSA public key", crt.PublicKey) 104 } 105 if err := rsa.VerifyPKCS1v15(pub, hashAlgo, payloadHash, sig); err != nil { 106 return false, nil //nolint:nilerr 107 } 108 return true, nil 109 }