Skip to content

Commit

Permalink
go/common/sgx/sigstruct: Add the ability to create SGX SIGSTRUCTS
Browse files Browse the repository at this point in the history
This structure has mountains of brain damage including but not limited
to:
 * 1 BCD encoded date.
 * 4 little endian(!) 3072 bit big ints.
 * 2 pre-computed big ints, because terrorizing developers is preferable
   to fully implementing RSA.
  • Loading branch information
Yawning committed May 7, 2020
1 parent a408af7 commit d794d8f
Show file tree
Hide file tree
Showing 5 changed files with 397 additions and 17 deletions.
57 changes: 40 additions & 17 deletions go/common/sgx/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import (
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"fmt"
"io"

"github.com/pkg/errors"
"math/big"
)

const (
Expand All @@ -20,6 +20,9 @@ const (

// enclaveIdentitySize is the total size of EnclaveIdentity in bytes.
enclaveIdentitySize = MrSignerSize + MrEnclaveSize

// ModulusSize is the required RSA modulus size in bits.
ModulusSize = 3072
)

// Mrenclave is a SGX enclave identity register value (MRENCLAVE).
Expand All @@ -34,7 +37,7 @@ func (m *MrEnclave) MarshalBinary() (data []byte, err error) {
// UnmarshalBinary decodes a binary marshaled Mrenclave.
func (m *MrEnclave) UnmarshalBinary(data []byte) error {
if len(data) != MrEnclaveSize {
return errors.New("sgx: malformed MRENCLAVE")
return fmt.Errorf("sgx: malformed MRENCLAVE")
}

copy(m[:], data)
Expand Down Expand Up @@ -70,7 +73,7 @@ readLoop:
case io.EOF:
break readLoop
default:
return errors.Wrap(err, "sgx: failed to read .sgxs")
return fmt.Errorf("sgx: failed to read .sgxs: %w", err)
}
}

Expand Down Expand Up @@ -102,7 +105,7 @@ func (m *MrSigner) MarshalBinary() (data []byte, err error) {
// UnmarshalBinary decodes a binary marshaled MrSigner.
func (m *MrSigner) UnmarshalBinary(data []byte) error {
if len(data) != MrSignerSize {
return errors.New("sgx: malformed MRSIGNER")
return fmt.Errorf("sgx: malformed MRSIGNER")
}

copy(m[:], data)
Expand All @@ -122,22 +125,42 @@ func (m *MrSigner) UnmarshalHex(text string) error {

// FromPublicKey derives a MrSigner from a RSA public key.
func (m *MrSigner) FromPublicKey(pk *rsa.PublicKey) error {
const modulusBits = 3072 // Hardware constraint.
if pk.Size() != modulusBits/8 {
return errors.New("sgx: invalid RSA public key for SGX signing")
}

// The MRSIGNER is the SHA256 digest of the little endian representation
// of the RSA public key modulus.
modulus := pk.N.Bytes()
for left, right := 0, len(modulus)-1; left < right; left, right = left+1, right-1 {
modulus[left], modulus[right] = modulus[right], modulus[left]
modulus, err := To3072le(pk.N, false)
if err != nil {
return err
}

sum := sha256.Sum256(modulus)
return m.UnmarshalBinary(sum[:])
}

// To3072le converts a big.Int to a 3072 bit little endian representation,
// padding if allowed AND required.
func To3072le(z *big.Int, mayPad bool) ([]byte, error) {
buf := z.Bytes()

const expectedSize = ModulusSize / 8
sz := len(buf)
if sz != expectedSize {
padLen := expectedSize - sz
if !mayPad || padLen < 0 {
return nil, fmt.Errorf("sgx: big int is not 3072 bits: %v", expectedSize)
}

// Pad before reversing.
padded := make([]byte, padLen, expectedSize)
buf = append(padded, buf...)
}

for left, right := 0, len(buf)-1; left < right; left, right = left+1, right-1 {
buf[left], buf[right] = buf[right], buf[left]
}

return buf, nil
}

// String returns the string representation of a MrSigner.
func (m MrSigner) String() string {
return hex.EncodeToString(m[:])
Expand All @@ -158,13 +181,13 @@ func (id EnclaveIdentity) MarshalText() (data []byte, err error) {
func (id *EnclaveIdentity) UnmarshalText(text []byte) error {
b, err := base64.StdEncoding.DecodeString(string(text))
if err != nil {
return errors.Wrap(err, "sgx: malformed EnclaveIdentity")
return fmt.Errorf("sgx: malformed EnclaveIdentity: %w", err)
}
if err := id.MrEnclave.UnmarshalBinary(b[:MrEnclaveSize]); err != nil {
return errors.Wrap(err, "sgx: malformed MrEnclave in EnclaveIdentity")
return fmt.Errorf("sgx: malformed MrEnclave in EnclaveIdentity: %w", err)
}
if err := id.MrSigner.UnmarshalBinary(b[MrEnclaveSize:]); err != nil {
return errors.Wrap(err, "sgx: malformed MrSigner in EnclaveIdentity")
return fmt.Errorf("sgx: malformed MrSigner in EnclaveIdentity: %w", err)
}

return nil
Expand All @@ -174,7 +197,7 @@ func (id *EnclaveIdentity) UnmarshalText(text []byte) error {
func (id *EnclaveIdentity) UnmarshalHex(text string) error {
b, err := hex.DecodeString(text)
if err != nil || len(b) != enclaveIdentitySize {
return errors.Wrap(err, "sgx: malformed EnclaveIdentity")
return fmt.Errorf("sgx: malformed EnclaveIdentity: %w", err)
}

copy(id.MrEnclave[:], b[:MrEnclaveSize])
Expand Down
257 changes: 257 additions & 0 deletions go/common/sgx/sigstruct/sigstruct.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
package sigstruct

import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"encoding/binary"
"encoding/hex"
"fmt"
"math/big"
"time"

"github.com/oasislabs/oasis-core/go/common/sgx"
"github.com/oasislabs/oasis-core/go/common/sgx/ias"
)

const (
sigstructSize = 1808

headerOffset = 0
vendorOffset = 16
dateOffset = 20
header2Offset = 24
swdefinedOffset = 40
modulusOffset = 128
exponentOffset = 512
signatureOffset = 516
miscSelectOffset = 900
miscSelectMaskOffset = 904
attributesOffset = 928
attributesMaskOffset = 944
enclaveHashOffset = 960
isvProdIDOffset = 1024
isvSVNOffset = 1026
q1Offset = 1040
q2Offset = 1424
)

var (
header = []byte{0x06, 0x00, 0x00, 0x00, 0xe1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}
header2 = []byte{0x01, 0x01, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00}
vendorNonIntel = []byte{0x00, 0x00, 0x00, 0x00}

requiredExponent = 3
)

// Sigstruct is an SGX enclave SIGSTRUCT.
//
// The most recent version of the Intel documentation defines more fields
// that were formerly reserved, however support for setting such things
// is currently not implemented.
type Sigstruct struct { //nolint: maligned
BuildDate time.Time
SwDefined [4]byte
MiscSelect uint32
MiscSelectMask uint32
Attributes ias.Attributes
AttributesMask [2]uint64
EnclaveHash sgx.MrEnclave
ISVProdID uint16
ISVSVN uint16
}

// Sign signs the SIGSTRUCT with the provided private key.
func (s *Sigstruct) Sign(privateKey *rsa.PrivateKey) ([]byte, error) {
// Check that the private key is sensible.
if e := privateKey.E; e != requiredExponent {
return nil, fmt.Errorf("sgx/sigstruct: invalid private key exponent: %v", e)
}
if bits := privateKey.Size(); bits != sgx.ModulusSize/8 {
return nil, fmt.Errorf("sgx/sigstruct: invalid RSA key size: %v", bits)
}

// Marshal the sigstruct to binary.
buf := s.toUnsigned()

// Generate the signature.
h := sha256.New()
_, _ = h.Write(buf[:modulusOffset])
_, _ = h.Write(buf[miscSelectOffset : isvSVNOffset+2])
hashed := h.Sum(nil)
rawSig, err := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA256, hashed)
if err != nil {
return nil, fmt.Errorf("sgx/sigstruct: RSA signing failed: %w", err)
}

// Generate the pre-computed bullshit.
sigBytes, q1Bytes, q2Bytes, err := postProcessSignature(rawSig, privateKey.N)
if err != nil {
return nil, err
}

// Fill out the rest of the SIGSTRUCT.
modBytes, _ := sgx.To3072le(privateKey.N, false) // Can't fail.
copy(buf[modulusOffset:], modBytes) // MODULUS
binary.LittleEndian.PutUint32(buf[exponentOffset:], uint32(privateKey.E)) // EXPONENT
copy(buf[signatureOffset:], sigBytes) // SIGNATURE
copy(buf[q1Offset:], q1Bytes) // Q1
copy(buf[q2Offset:], q2Bytes) // Q2

return buf, nil
}

func postProcessSignature(raw []byte, modulus *big.Int) (sigBytes, q1Bytes, q2Bytes []byte, err error) {
var sig big.Int
sig.SetBytes(raw)

if sigBytes, err = sgx.To3072le(&sig, true); err != nil {
return nil, nil, nil, fmt.Errorf("sgx/sigstruct: failed to serialize signature: %w", err)
}

// q1 = floor(Signature^2 / Modulus);
// q2 = floor((Signature^3 - q1 * Signature * Modulus) / Modulus);
var q1, q2, toSub big.Int
q1.Mul(&sig, &sig) // q1 = sig^2
q2.Mul(&q1, &sig) // q2 = sig^3
q1.Div(&q1, modulus) // q1 = floor(q1 / modulus)

toSub.Mul(&q1, &sig) // toSub = q1 * sig
toSub.Mul(&toSub, modulus) // toSub = toSub * modulus
q2.Sub(&q2, &toSub) // q2 = q2 - toSub
q2.Div(&q2, modulus) // floor(q2 = q2 / modulus)

if q1Bytes, err = sgx.To3072le(&q1, true); err != nil {
return nil, nil, nil, fmt.Errorf("sgx/sigstruct: failed to serialize q1: %w", err)
}
if q2Bytes, err = sgx.To3072le(&q2, true); err != nil {
return nil, nil, nil, fmt.Errorf("sgx/sigstruct: failed to serialize q2: %w", err)
}

return
}

func (s *Sigstruct) toUnsigned() []byte {
var buf [sigstructSize]byte

// See:
// Intel 64 and IA-32 Architectures Software Developer’s Manual
// 37.14 ENCLAVE SIGNATURE STRUCTURE (SIGSTRUCT)
copy(buf[headerOffset:], header) // HEADER
copy(buf[vendorOffset:], vendorNonIntel) // VENDOR
binary.LittleEndian.PutUint32(buf[dateOffset:], toBcdDate(s.BuildDate)) // DATE
copy(buf[header2Offset:], header2) // HEADER2
copy(buf[swdefinedOffset:], s.SwDefined[:]) // SWDEFINED
// RESERVED
// MODULUS (Not covered by signature)
// EXPONENT (Not covered by signature)
// SIGNATURE (Not covered by signature)
binary.LittleEndian.PutUint32(buf[miscSelectOffset:], s.MiscSelect) // MISCSELECT
binary.LittleEndian.PutUint32(buf[miscSelectMaskOffset:], s.MiscSelectMask) // MISCMASK
// CET_ATTRIBUTES
// CET_ATTRIBUTES_MASK
// RESERVED
// ISVFAMILYID
binary.LittleEndian.PutUint64(buf[attributesOffset:], uint64(s.Attributes.Flags)) // ATTRIBUTES (flags)
binary.LittleEndian.PutUint64(buf[attributesOffset+8:], s.Attributes.Xfrm) // ATTRIBUTES (xfrm)
binary.LittleEndian.PutUint64(buf[attributesMaskOffset:], s.AttributesMask[0]) // ATTRIBUTEMASK (flags)
binary.LittleEndian.PutUint64(buf[attributesMaskOffset+8:], s.AttributesMask[1]) // ATTRIBUTEMASK (xfrm)
copy(buf[enclaveHashOffset:], s.EnclaveHash[:]) // ENCLAVEHASH
// RESERVED
// ISVEXTPRODID
binary.LittleEndian.PutUint16(buf[isvProdIDOffset:], s.ISVProdID) // ISVPRODID
binary.LittleEndian.PutUint16(buf[isvSVNOffset:], s.ISVSVN) // ISVSVN
// RESERVED
// Q1 (Not covered by signature)
// Q2 (Not covered by signature)

return buf[:]
}

func toBcdDate(t time.Time) uint32 {
// The DATE field is encoded as yyyymmdd BCD, little endian.
y, m, d := t.Date()
s := fmt.Sprintf("%4d%02d%02d", y, m, d)
v, err := hex.DecodeString(s)
if err != nil {
panic(err)
}
return binary.BigEndian.Uint32(v)
}

// Option is an option used when constructing a Sigstruct.
type Option func(*Sigstruct)

// WithBuildDate sets the BUILDDATE field.
func WithBuildDate(date time.Time) Option {
return func(s *Sigstruct) {
s.BuildDate = date
}
}

// WithSwDefined sets the SWDEFINED field.
func WithSwDefined(swDefined [4]byte) Option {
return func(s *Sigstruct) {
s.SwDefined = swDefined
}
}

// WithMiscSelect sets the MISCSELECT field.
func WithMiscSelect(miscSelect uint32) Option {
return func(s *Sigstruct) {
s.MiscSelect = miscSelect
}
}

// WithMiscSelectMask sets the MISCSELECTMASK field.
func WithMiscSelectMask(miscSelectMask uint32) Option {
return func(s *Sigstruct) {
s.MiscSelectMask = miscSelectMask
}
}

// WithAttributes sets the ATTRIBUTES field.
func WithAttributes(attributes ias.Attributes) Option {
return func(s *Sigstruct) {
s.Attributes = attributes
}
}

// WithAttributesMask sets the ATTRIBUTESMASK field.
func WithAttributesMask(attributesMask [2]uint64) Option {
return func(s *Sigstruct) {
s.AttributesMask = attributesMask
}
}

// WithEnclaveHash sets the ENCLAVEHASH field.
func WithEnclaveHash(enclaveHash sgx.MrEnclave) Option {
return func(s *Sigstruct) {
s.EnclaveHash = enclaveHash
}
}

// WithISVProdID sets the ISVPRODID field.
func WithISVProdID(isvProdID uint16) Option {
return func(s *Sigstruct) {
s.ISVProdID = isvProdID
}
}

// WithISVSVN sets the ISVSVN field.
func WithISVSVN(isvSVN uint16) Option {
return func(s *Sigstruct) {
s.ISVSVN = isvSVN
}
}

// New creates a new Sigstruct ready to be signed.
func New(opts ...Option) *Sigstruct {
var s Sigstruct
for _, v := range opts {
v(&s)
}

return &s
}
Loading

0 comments on commit d794d8f

Please sign in to comment.