// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved. // See the file LICENSE for licensing terms. package staking import ( "crypto" "crypto/ecdsa" "crypto/elliptic" "crypto/rsa" "encoding/asn1" "errors" "fmt" "math/big" "golang.org/x/crypto/cryptobyte" "github.com/luxfi/constants" cryptobyte_asn1 "golang.org/x/crypto/cryptobyte/asn1" ) const ( MaxCertificateLen = 2 * constants.KiB allowedRSASmallModulusLen = 2048 allowedRSALargeModulusLen = 4096 allowedRSAPublicExponentValue = 65537 ) var ( ErrCertificateTooLarge = fmt.Errorf("staking: certificate length is greater than %d", MaxCertificateLen) ErrMalformedCertificate = errors.New("staking: malformed certificate") ErrMalformedTBSCertificate = errors.New("staking: malformed tbs certificate") ErrMalformedVersion = errors.New("staking: malformed version") ErrMalformedSerialNumber = errors.New("staking: malformed serial number") ErrMalformedSignatureAlgorithmIdentifier = errors.New("staking: malformed signature algorithm identifier") ErrMalformedIssuer = errors.New("staking: malformed issuer") ErrMalformedValidity = errors.New("staking: malformed validity") ErrMalformedSPKI = errors.New("staking: malformed spki") ErrMalformedPublicKeyAlgorithmIdentifier = errors.New("staking: malformed public key algorithm identifier") ErrMalformedSubjectPublicKey = errors.New("staking: malformed subject public key") ErrMalformedOID = errors.New("staking: malformed oid") ErrInvalidRSAPublicKey = errors.New("staking: invalid RSA public key") ErrInvalidRSAModulus = errors.New("staking: invalid RSA modulus") ErrInvalidRSAPublicExponent = errors.New("staking: invalid RSA public exponent") ErrRSAModulusNotPositive = errors.New("staking: RSA modulus is not a positive number") ErrUnsupportedRSAModulusBitLen = errors.New("staking: unsupported RSA modulus bitlen") ErrRSAModulusIsEven = errors.New("staking: RSA modulus is an even number") ErrUnsupportedRSAPublicExponent = errors.New("staking: unsupported RSA public exponent") ErrFailedUnmarshallingEllipticCurvePoint = errors.New("staking: failed to unmarshal elliptic curve point") ErrUnknownPublicKeyAlgorithm = errors.New("staking: unknown public key algorithm") ) // ParseCertificate parses a single certificate from the given ASN.1. // // This function does not validate that the certificate is valid to be used // against normal TLS implementations. // // Ref: https://github.com/golang/go/blob/go1.19.12/src/crypto/x509/parser.go#L789-L968 func ParseCertificate(bytes []byte) (*Certificate, error) { if len(bytes) > MaxCertificateLen { return nil, ErrCertificateTooLarge } input := cryptobyte.String(bytes) // Consume the length and tag bytes. if !input.ReadASN1(&input, cryptobyte_asn1.SEQUENCE) { return nil, ErrMalformedCertificate } // Read the "to be signed" certificate into input. if !input.ReadASN1(&input, cryptobyte_asn1.SEQUENCE) { return nil, ErrMalformedTBSCertificate } if !input.SkipOptionalASN1(cryptobyte_asn1.Tag(0).Constructed().ContextSpecific()) { return nil, ErrMalformedVersion } if !input.SkipASN1(cryptobyte_asn1.INTEGER) { return nil, ErrMalformedSerialNumber } if !input.SkipASN1(cryptobyte_asn1.SEQUENCE) { return nil, ErrMalformedSignatureAlgorithmIdentifier } if !input.SkipASN1(cryptobyte_asn1.SEQUENCE) { return nil, ErrMalformedIssuer } if !input.SkipASN1(cryptobyte_asn1.SEQUENCE) { return nil, ErrMalformedValidity } if !input.SkipASN1(cryptobyte_asn1.SEQUENCE) { return nil, ErrMalformedIssuer } // Read the "subject public key info" into input. if !input.ReadASN1(&input, cryptobyte_asn1.SEQUENCE) { return nil, ErrMalformedSPKI } // Read the public key algorithm identifier. var pkAISeq cryptobyte.String if !input.ReadASN1(&pkAISeq, cryptobyte_asn1.SEQUENCE) { return nil, ErrMalformedPublicKeyAlgorithmIdentifier } var pkAI asn1.ObjectIdentifier if !pkAISeq.ReadASN1ObjectIdentifier(&pkAI) { return nil, ErrMalformedOID } // Note: Unlike the x509 package, we require parsing the public key. var spk asn1.BitString if !input.ReadASN1BitString(&spk) { return nil, ErrMalformedSubjectPublicKey } publicKey, err := parsePublicKey(pkAI, spk) return &Certificate{ Raw: bytes, PublicKey: publicKey, }, err } // Ref: https://github.com/golang/go/blob/go1.19.12/src/crypto/x509/parser.go#L215-L306 func parsePublicKey(oid asn1.ObjectIdentifier, publicKey asn1.BitString) (crypto.PublicKey, error) { der := cryptobyte.String(publicKey.RightAlign()) switch { case oid.Equal(oidPublicKeyRSA): pub := &rsa.PublicKey{N: new(big.Int)} if !der.ReadASN1(&der, cryptobyte_asn1.SEQUENCE) { return nil, ErrInvalidRSAPublicKey } if !der.ReadASN1Integer(pub.N) { return nil, ErrInvalidRSAModulus } if !der.ReadASN1Integer(&pub.E) { return nil, ErrInvalidRSAPublicExponent } if err := ValidateRSAPublicKeyIsWellFormed(pub); err != nil { return nil, err } return pub, nil case oid.Equal(oidPublicKeyECDSA): namedCurve := elliptic.P256() x, y := elliptic.Unmarshal(namedCurve, der) if x == nil { return nil, ErrFailedUnmarshallingEllipticCurvePoint } return &ecdsa.PublicKey{ Curve: namedCurve, X: x, Y: y, }, nil default: return nil, ErrUnknownPublicKeyAlgorithm } } // ValidateRSAPublicKeyIsWellFormed validates the given RSA public key func ValidateRSAPublicKeyIsWellFormed(pub *rsa.PublicKey) error { if pub == nil { return ErrInvalidRSAPublicKey } if pub.N.Sign() <= 0 { return ErrRSAModulusNotPositive } if bitLen := pub.N.BitLen(); bitLen != allowedRSALargeModulusLen && bitLen != allowedRSASmallModulusLen { return fmt.Errorf("%w: %d", ErrUnsupportedRSAModulusBitLen, bitLen) } if pub.N.Bit(0) == 0 { return ErrRSAModulusIsEven } if pub.E != allowedRSAPublicExponentValue { return fmt.Errorf("%w: %d", ErrUnsupportedRSAPublicExponent, pub.E) } return nil }