aws_signing_helper/tpm_signer.go (363 lines of code) (raw):

package aws_signing_helper import ( "crypto" "crypto/ecdsa" "crypto/sha1" "crypto/sha256" "crypto/sha512" "crypto/x509" "encoding/asn1" "encoding/pem" "errors" "fmt" "io" "math/big" "strconv" "strings" tpm2 "github.com/google/go-tpm/tpm2" tpmutil "github.com/google/go-tpm/tpmutil" ) type tpm2_TPMPolicy struct { CommandCode int `asn1:"explicit,tag:0"` CommandPolicy []byte `asn1:"explicit,tag:1"` } type tpm2_TPMAuthPolicy struct { Name string `asn1:"utf8,optional,explicit,tag:0"` Policy []tpm2_TPMPolicy `asn1:"explicit,tag:1"` } type tpm2_TPMKey struct { Oid asn1.ObjectIdentifier EmptyAuth bool `asn1:"optional,explicit,tag:0"` Policy []tpm2_TPMPolicy `asn1:"optional,explicit,tag:1"` Secret []byte `asn1:"optional,explicit,tag:2"` AuthPolicy []tpm2_TPMAuthPolicy `asn1:"optional,explicit,tag:3"` Parent int Pubkey []byte Privkey []byte } var oidLoadableKey = asn1.ObjectIdentifier{2, 23, 133, 10, 1, 3} var TPM_RC_AUTH_FAIL = "0x22" type TPMv2Signer struct { cert *x509.Certificate certChain []*x509.Certificate tpmData tpm2_TPMKey public tpm2.Public private []byte password string emptyAuth bool handle tpmutil.Handle } func handleIsPersistent(h int) bool { return (h >> 24) == int(tpm2.HandleTypePersistent) } var primaryParams = tpm2.Public{ Type: tpm2.AlgECC, NameAlg: tpm2.AlgSHA256, Attributes: tpm2.FlagUserWithAuth | tpm2.FlagRestricted | tpm2.FlagDecrypt | tpm2.FlagFixedTPM | tpm2.FlagFixedParent | tpm2.FlagNoDA | tpm2.FlagSensitiveDataOrigin, ECCParameters: &tpm2.ECCParams{ Symmetric: &tpm2.SymScheme{ Alg: tpm2.AlgAES, KeyBits: 128, Mode: tpm2.AlgCFB, }, Sign: &tpm2.SigScheme{ Alg: tpm2.AlgNull, }, CurveID: tpm2.CurveNISTP256, KDF: &tpm2.KDFScheme{ Alg: tpm2.AlgNull, }, }, } type GetTPMv2SignerOpts struct { certificate *x509.Certificate certificateChain []*x509.Certificate keyPem *pem.Block password string emptyAuth bool handle string } // Returns the public key associated with this TPMv2Signer func (tpmv2Signer *TPMv2Signer) Public() crypto.PublicKey { ret, _ := tpmv2Signer.public.Key() return ret } // Closes this TPMv2Signer func (tpmv2Signer *TPMv2Signer) Close() { tpmv2Signer.password = "" } func checkCapability(rw io.ReadWriter, algo tpm2.Algorithm) error { descs, _, err := tpm2.GetCapability(rw, tpm2.CapabilityAlgs, 1, uint32(algo)) if err != nil { errMsg := fmt.Sprintf("error trying to get capability from TPM for the algorithm (%s)", algo) return errors.New(errMsg) } if tpm2.Algorithm(descs[0].(tpm2.AlgorithmDescription).ID) != algo { errMsg := fmt.Sprintf("unsupported algorithm (%s) for TPM", algo) return errors.New(errMsg) } return nil } // Implements the crypto.Signer interface and signs the passed in digest func (tpmv2Signer *TPMv2Signer) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) { var ( keyHandle tpmutil.Handle ) rw, err := openTPM() if err != nil { return nil, err } defer rw.Close() if tpmv2Signer.handle != 0 { keyHandle = tpmv2Signer.handle } else { parentHandle := tpmutil.Handle(tpmv2Signer.tpmData.Parent) if !handleIsPersistent(tpmv2Signer.tpmData.Parent) { // Parent and owner passwords aren't supported currently when creating a primary given a persistent handle for the parent parentHandle, _, err = tpm2.CreatePrimary(rw, tpmutil.Handle(tpmv2Signer.tpmData.Parent), tpm2.PCRSelection{}, "", "", primaryParams) if err != nil { return nil, err } defer tpm2.FlushContext(rw, parentHandle) } keyHandle, _, err = tpm2.Load(rw, parentHandle, "", tpmv2Signer.tpmData.Pubkey[2:], tpmv2Signer.tpmData.Privkey[2:]) if err != nil { return nil, err } defer tpm2.FlushContext(rw, keyHandle) } var algo tpm2.Algorithm var shadigest []byte switch opts.HashFunc() { case crypto.SHA256: sha256digest := sha256.Sum256(digest) shadigest = sha256digest[:] algo = tpm2.AlgSHA256 case crypto.SHA384: sha384digest := sha512.Sum384(digest) shadigest = sha384digest[:] algo = tpm2.AlgSHA384 case crypto.SHA512: sha512digest := sha512.Sum512(digest) shadigest = sha512digest[:] algo = tpm2.AlgSHA512 } if tpmv2Signer.public.Type == tpm2.AlgECC { // Check to see that ECDSA is supported for signing err = checkCapability(rw, tpm2.AlgECC) if err != nil { return nil, err } // For an EC key we lie to the TPM about what the hash is. // It doesn't actually matter what the original digest was; // the algo we feed to the TPM is *purely* based on the // size of the curve itself. We truncate the actual digest, // or pad with zeroes, to the byte size of the key. pubKey, err := tpmv2Signer.public.Key() if err != nil { return nil, err } ecPubKey, ok := pubKey.(*ecdsa.PublicKey) if !ok { return nil, errors.New("failed to obtain ecdsa.PublicKey") } bitSize := ecPubKey.Curve.Params().BitSize byteSize := (bitSize + 7) / 8 if byteSize > sha512.Size { byteSize = sha512.Size } switch byteSize { case sha512.Size: algo = tpm2.AlgSHA512 case sha512.Size384: algo = tpm2.AlgSHA384 case sha512.Size256: algo = tpm2.AlgSHA256 case sha1.Size: algo = tpm2.AlgSHA1 default: return nil, errors.New("unsupported curve") } if len(shadigest) > byteSize { shadigest = shadigest[:byteSize] } for len(shadigest) < byteSize { shadigest = append([]byte{0}, shadigest...) } sig, err := tpmv2Signer.signHelper(rw, keyHandle, shadigest, &tpm2.SigScheme{Alg: tpm2.AlgECDSA, Hash: algo}) if err != nil { return nil, err } signature, err = asn1.Marshal(struct { R *big.Int S *big.Int }{sig.ECC.R, sig.ECC.S}) if err != nil { return nil, err } } else { // Check to see that the requested hash function is supported err = checkCapability(rw, algo) if err != nil { return nil, err } // Check to see that RSASSA is supported for signing err = checkCapability(rw, tpm2.AlgRSASSA) if err != nil { return nil, err } sig, err := tpmv2Signer.signHelper(rw, keyHandle, shadigest, &tpm2.SigScheme{Alg: tpm2.AlgRSASSA, Hash: algo}) if err != nil { return nil, err } signature = sig.RSA.Signature } return signature, nil } func (tpmv2Signer *TPMv2Signer) signHelper(rw io.ReadWriter, keyHandle tpmutil.Handle, digest tpmutil.U16Bytes, sigScheme *tpm2.SigScheme) (*tpm2.Signature, error) { passwordPromptInput := PasswordPromptProps{ InitialPassword: tpmv2Signer.password, NoPassword: tpmv2Signer.emptyAuth, CheckPassword: func(password string) (interface{}, error) { return tpm2.Sign(rw, keyHandle, password, digest, nil, sigScheme) }, IncorrectPasswordMsg: "incorrect TPM key password", Prompt: "Please enter your TPM key password:", Reprompt: "Incorrect TPM key password. Please try again:", ParseErrMsg: "unable to read your TPM key password", CheckPasswordAuthorizationErrorMsg: TPM_RC_AUTH_FAIL, } password, sig, err := PasswordPrompt(passwordPromptInput) if err != nil { return nil, err } tpmv2Signer.password = password return sig.(*tpm2.Signature), err } // Gets the x509.Certificate associated with this TPMv2Signer func (tpmv2Signer *TPMv2Signer) Certificate() (*x509.Certificate, error) { return tpmv2Signer.cert, nil } // Gets the certificate chain associated with this TPMv2Signer func (tpmv2Signer *TPMv2Signer) CertificateChain() (chain []*x509.Certificate, err error) { return tpmv2Signer.certChain, nil } /* * DER forbids storing a BOOLEAN as anything but 0x00 or 0xFF, * 0x01, and the Go asn1 parser cannot be relaxed. But both * OpenSSL ENGINEs which produce these keys have at least in * the past emitted 0x01 as the value, leading to an Unmarshal * failure with 'asn1: syntax error: invalid boolean'. So... */ func fixupEmptyAuth(tpmData *[]byte) { var pos int = 0 // Skip the SEQUENCE tag and length if len(*tpmData) < 2 || (*tpmData)[0] != 0x30 { return } // Don't care what the SEQUENCE length is, just skip it pos = 1 lenByte := (*tpmData)[pos] if lenByte < 0x80 { pos = pos + 1 } else if lenByte < 0x85 { pos = pos + 1 + int(lenByte) - 0x80 } else { return } if len(*tpmData) <= pos { return } // Use asn1.Unmarshal to eat the OID; we care about 'rest' var oid asn1.ObjectIdentifier rest, err := asn1.Unmarshal((*tpmData)[pos:], &oid) if err != nil || rest == nil || !oid.Equal(oidLoadableKey) || len(rest) < 5 { return } // If the OPTIONAL EXPLICIT BOOLEAN [0] exists, it'll be here pos = len(*tpmData) - len(rest) if (*tpmData)[pos] == 0xa0 && // Tag (*tpmData)[pos+1] == 0x03 && // length (*tpmData)[pos+2] == 0x01 && (*tpmData)[pos+3] == 0x01 && (*tpmData)[pos+4] == 0x01 { (*tpmData)[pos+4] = 0xff } } // Returns a TPMv2Signer, that can be used to sign a payload through a TPMv2-compatible // cryptographic device func GetTPMv2Signer(opts GetTPMv2SignerOpts) (signer Signer, signingAlgorithm string, err error) { var ( certificate *x509.Certificate certificateChain []*x509.Certificate keyPem *pem.Block password string emptyAuth bool tpmData tpm2_TPMKey handle tpmutil.Handle public tpm2.Public private []byte ) certificate = opts.certificate certificateChain = opts.certificateChain keyPem = opts.keyPem password = opts.password emptyAuth = opts.emptyAuth // If a handle is provided instead of a TPM key file if opts.handle != "" { handleParts := strings.Split(opts.handle, ":") if len(handleParts) != 2 { return nil, "", errors.New("invalid TPM handle format") } hexHandleStr := handleParts[1] if strings.HasPrefix(hexHandleStr, "0x") { hexHandleStr = hexHandleStr[2:] } handleValue, err := strconv.ParseUint(hexHandleStr, 16, 32) if err != nil { return nil, "", errors.New("invalid hex TPM handle value") } handle = tpmutil.Handle(handleValue) // Read the public key from the loaded key within the TPM rw, err := openTPM() if err != nil { return nil, "", err } defer rw.Close() public, _, _, err = tpm2.ReadPublic(rw, handle) if err != nil { return nil, "", err } } else { fixupEmptyAuth(&keyPem.Bytes) _, err = asn1.Unmarshal(keyPem.Bytes, &tpmData) if err != nil { return nil, "", err } emptyAuth = tpmData.EmptyAuth if emptyAuth && password != "" { return nil, "", errors.New("password is provided but TPM key file indicates that one isn't required") } if !tpmData.Oid.Equal(oidLoadableKey) { return nil, "", errors.New("invalid OID for TPMv2 key:" + tpmData.Oid.String()) } if tpmData.Policy != nil || tpmData.AuthPolicy != nil { return nil, "", errors.New("TPMv2 policy not implemented yet") } if tpmData.Secret != nil { return nil, "", errors.New("TPMv2 key has 'secret' field which should not be set") } if !handleIsPersistent(tpmData.Parent) && tpmData.Parent != int(tpm2.HandleOwner) && tpmData.Parent != int(tpm2.HandleNull) && tpmData.Parent != int(tpm2.HandleEndorsement) && tpmData.Parent != int(tpm2.HandlePlatform) { return nil, "", errors.New("invalid parent for TPMv2 key") } if len(tpmData.Pubkey) < 2 || len(tpmData.Pubkey)-2 != (int(tpmData.Pubkey[0])<<8)+int(tpmData.Pubkey[1]) { return nil, "", errors.New("invalid length for TPMv2 PUBLIC blob") } public, err = tpm2.DecodePublic(tpmData.Pubkey[2:]) if err != nil { return nil, "", err } if len(tpmData.Privkey) < 2 || len(tpmData.Privkey)-2 != (int(tpmData.Privkey[0])<<8)+int(tpmData.Privkey[1]) { return nil, "", errors.New("invalid length for TPMv2 PRIVATE blob") } private = tpmData.Privkey[2:] } switch public.Type { case tpm2.AlgRSA: signingAlgorithm = aws4_x509_rsa_sha256 case tpm2.AlgECC: signingAlgorithm = aws4_x509_ecdsa_sha256 default: return nil, "", errors.New("unsupported TPMv2 key type") } return &TPMv2Signer{ certificate, certificateChain, tpmData, public, private, password, emptyAuth, handle, }, signingAlgorithm, nil }