mirror of
https://github.com/luxfi/staking.git
synced 2026-07-27 03:51:29 +00:00
178 lines
6.1 KiB
Go
178 lines
6.1 KiB
Go
// 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
|
|
}
|