mirror of
https://github.com/blacktop/ipsw.git
synced 2026-05-08 12:22:26 +00:00
488 lines
12 KiB
Go
488 lines
12 KiB
Go
package cms
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto"
|
|
"crypto/ecdsa"
|
|
"crypto/elliptic"
|
|
"crypto/rand"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/asn1"
|
|
"errors"
|
|
"fmt"
|
|
"math/big"
|
|
"sort"
|
|
"time"
|
|
|
|
"github.com/blacktop/go-plist"
|
|
"github.com/blacktop/ipsw/internal/codesign/cms/oid"
|
|
)
|
|
|
|
// CREDIT - https://github.com/github/smimesign/tree/main/ietf-cms
|
|
|
|
// AnySet is a helper for dealing with SET OF ANY types.
|
|
type AnySet struct {
|
|
Elements []asn1.RawValue `asn1:"set"`
|
|
}
|
|
|
|
// NewAnySet creates a new AnySet.
|
|
func NewAnySet(elts ...asn1.RawValue) AnySet {
|
|
return AnySet{elts}
|
|
}
|
|
|
|
// DecodeAnySet manually decodes a SET OF ANY type, since Go's parser can't
|
|
// handle them.
|
|
func DecodeAnySet(rv asn1.RawValue) (as AnySet, err error) {
|
|
if rv.Class != asn1.ClassUniversal || rv.Tag != asn1.TagSet {
|
|
err = fmt.Errorf("bad class: expecting %d, got %d", asn1.ClassUniversal, rv.Class)
|
|
return
|
|
}
|
|
|
|
der := rv.Bytes
|
|
for len(der) > 0 {
|
|
if der, err = asn1.Unmarshal(der, &rv); err != nil {
|
|
return
|
|
}
|
|
|
|
as.Elements = append(as.Elements, rv)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// Encode manually encodes a SET OF ANY type, since Go's parser can't handle
|
|
// them.
|
|
func (as AnySet) Encode(dst *asn1.RawValue) (err error) {
|
|
dst.Class = asn1.ClassUniversal
|
|
dst.Tag = asn1.TagSet
|
|
dst.IsCompound = true
|
|
|
|
var der []byte
|
|
for _, elt := range as.Elements {
|
|
if der, err = asn1.Marshal(elt); err != nil {
|
|
return
|
|
}
|
|
|
|
dst.Bytes = append(dst.Bytes, der...)
|
|
}
|
|
|
|
dst.FullBytes, err = asn1.Marshal(*dst)
|
|
|
|
return
|
|
}
|
|
|
|
type Attribute struct {
|
|
Type asn1.ObjectIdentifier
|
|
// This should be a SET OF ANY, but Go's asn1 parser can't handle slices of
|
|
// RawValues. Use value() to get an AnySet of the value.
|
|
RawValue asn1.RawValue
|
|
}
|
|
|
|
type Attributes []Attribute
|
|
|
|
func (attrs Attributes) MarshaledForSigning() ([]byte, error) {
|
|
seq, err := asn1.Marshal(struct {
|
|
Attributes `asn1:"set"`
|
|
}{attrs})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// unwrap the outer SEQUENCE
|
|
var raw asn1.RawValue
|
|
if _, err = asn1.Unmarshal(seq, &raw); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return raw.Bytes, nil
|
|
}
|
|
|
|
type SignerInfo struct {
|
|
Version int
|
|
SID asn1.RawValue
|
|
DigestAlgorithm pkix.AlgorithmIdentifier
|
|
SignedAttrs Attributes `asn1:"optional,tag:0"`
|
|
SignatureAlgorithm pkix.AlgorithmIdentifier
|
|
Signature []byte
|
|
UnsignedAttrs Attributes `asn1:"set,optional,tag:1"`
|
|
}
|
|
|
|
func (si SignerInfo) Hash() (crypto.Hash, error) {
|
|
algo := si.DigestAlgorithm.Algorithm.String()
|
|
hash := oid.DigestAlgorithmToCryptoHash[algo]
|
|
if hash == 0 || !hash.Available() {
|
|
return 0, fmt.Errorf("unsupported digest algorithm: %s", algo)
|
|
}
|
|
return hash, nil
|
|
}
|
|
|
|
type EncapsulatedContentInfo struct {
|
|
EContentType asn1.ObjectIdentifier
|
|
EContent asn1.RawValue `asn1:"optional,explicit,tag:0"`
|
|
}
|
|
|
|
func (eci EncapsulatedContentInfo) EContentValue() ([]byte, error) {
|
|
if eci.EContent.Bytes == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
// The EContent is an `[0] EXPLICIT OCTET STRING`. EXPLICIT means that there
|
|
// is another whole tag wrapping the OCTET STRING. When we decoded the
|
|
// EContent into a asn1.RawValue we're just getting that outer tag, so the
|
|
// EContent.Bytes is the encoded OCTET STRING, which is what we really want
|
|
// the value of.
|
|
var octets asn1.RawValue
|
|
if rest, err := asn1.Unmarshal(eci.EContent.Bytes, &octets); err != nil {
|
|
return nil, err
|
|
} else if len(rest) > 0 {
|
|
return nil, fmt.Errorf("unexpected trailing data after EContent: %x", rest)
|
|
}
|
|
if octets.Class != asn1.ClassUniversal || octets.Tag != asn1.TagOctetString {
|
|
return nil, fmt.Errorf("unexpected EContent tag: %d; or class: %d", octets.Tag, octets.Class)
|
|
}
|
|
|
|
// While we already tried converting BER to DER, we didn't take constructed
|
|
// types into account. Constructed string types, as opposed to primitive
|
|
// types, can encode indefinite length strings by including a bunch of
|
|
// sub-strings that are joined together to get the actual value. Gpgsm uses
|
|
// a constructed OCTET STRING for the EContent, so we have to manually decode
|
|
// it here.
|
|
var value []byte
|
|
if octets.IsCompound {
|
|
rest := octets.Bytes
|
|
for len(rest) > 0 {
|
|
var err error
|
|
if rest, err = asn1.Unmarshal(rest, &octets); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Don't allow further constructed types.
|
|
if octets.Class != asn1.ClassUniversal || octets.Tag != asn1.TagOctetString || octets.IsCompound {
|
|
return nil, fmt.Errorf("unexpected EContent tag: %d; or class: %d", octets.Tag, octets.Class)
|
|
}
|
|
|
|
value = append(value, octets.Bytes...)
|
|
}
|
|
} else {
|
|
value = octets.Bytes
|
|
}
|
|
|
|
return value, nil
|
|
}
|
|
|
|
type SignedData struct {
|
|
Version int
|
|
DigestAlgorithms []pkix.AlgorithmIdentifier `asn1:"set"`
|
|
EncapContentInfo EncapsulatedContentInfo
|
|
Certificates []asn1.RawValue `asn1:"optional,set,tag:0"`
|
|
CRLs []asn1.RawValue `asn1:"optional,set,tag:1"`
|
|
SignerInfos []SignerInfo `asn1:"set"`
|
|
}
|
|
|
|
type IssuerAndSerialNumber struct {
|
|
Issuer asn1.RawValue
|
|
SerialNumber *big.Int
|
|
}
|
|
|
|
type ContentInfo struct {
|
|
ContentType asn1.ObjectIdentifier
|
|
Content asn1.RawValue `asn1:"explicit,tag:0"`
|
|
}
|
|
|
|
type AppleHashAgility struct {
|
|
Type asn1.ObjectIdentifier
|
|
Content asn1.RawValue `asn1:"explicit,tag:0"`
|
|
}
|
|
|
|
type CDHash struct {
|
|
CDHashes [][]byte `plist:"cdhashes,omitempty" xml:"cdhashes,omitempty"`
|
|
}
|
|
|
|
func NewSignedData(data []byte) (*SignedData, error) {
|
|
octets, err := asn1.Marshal(asn1.RawValue{
|
|
Class: asn1.ClassUniversal,
|
|
Tag: asn1.TagOctetString,
|
|
Bytes: data,
|
|
IsCompound: false,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &SignedData{
|
|
Version: 1,
|
|
DigestAlgorithms: []pkix.AlgorithmIdentifier{},
|
|
EncapContentInfo: EncapsulatedContentInfo{
|
|
EContentType: oid.ContentTypeData,
|
|
EContent: asn1.RawValue{
|
|
Class: asn1.ClassContextSpecific,
|
|
Tag: 0,
|
|
Bytes: octets,
|
|
IsCompound: true,
|
|
},
|
|
},
|
|
SignerInfos: []SignerInfo{},
|
|
}, nil
|
|
}
|
|
|
|
func (sd *SignedData) Sign(chain []*x509.Certificate, privateKey any) error {
|
|
var certPub []byte
|
|
var cert *x509.Certificate
|
|
|
|
signer, ok := privateKey.(crypto.Signer)
|
|
if !ok {
|
|
return errors.New("private key does not implement crypto.Signer")
|
|
}
|
|
|
|
pub, err := x509.MarshalPKIXPublicKey(signer.Public())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, c := range chain {
|
|
if err = sd.AddCertificate(c); err != nil {
|
|
return err
|
|
}
|
|
|
|
if certPub, err = x509.MarshalPKIXPublicKey(c.PublicKey); err != nil {
|
|
return err
|
|
}
|
|
|
|
if bytes.Equal(pub, certPub) {
|
|
cert = c
|
|
}
|
|
}
|
|
if cert == nil {
|
|
return errors.New("no certificate found for public key")
|
|
}
|
|
|
|
sid, err := newIssuerAndSerialNumber(cert)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
digestAlgorithmID := digestAlgorithmForPublicKey(pub)
|
|
|
|
signatureAlgorithmOID, ok := oid.X509PublicKeyAndDigestAlgorithmToSignatureAlgorithm[cert.PublicKeyAlgorithm][digestAlgorithmID.Algorithm.String()]
|
|
if !ok {
|
|
return errors.New("unsupported certificate public key algorithm")
|
|
}
|
|
|
|
signatureAlgorithmID := pkix.AlgorithmIdentifier{Algorithm: signatureAlgorithmOID}
|
|
|
|
si := SignerInfo{
|
|
Version: 1,
|
|
SID: sid,
|
|
DigestAlgorithm: digestAlgorithmID,
|
|
SignedAttrs: nil,
|
|
SignatureAlgorithm: signatureAlgorithmID,
|
|
Signature: nil,
|
|
UnsignedAttrs: nil,
|
|
}
|
|
|
|
// Get the message
|
|
content, err := sd.EncapContentInfo.EContentValue()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if content == nil {
|
|
return errors.New("already detached")
|
|
}
|
|
|
|
// Digest the message.
|
|
hash, err := si.Hash()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
md := hash.New()
|
|
if _, err = md.Write(content); err != nil {
|
|
return err
|
|
}
|
|
pldata, err := plist.MarshalIndent(CDHash{
|
|
CDHashes: [][]byte{md.Sum(nil)[:20]},
|
|
}, plist.XMLFormat, "\t")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Build our SignedAttributes
|
|
stAttr, err := NewAttribute(oid.AttributeSigningTime, time.Now().UTC())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
mdAttr, err := NewAttribute(oid.AttributeMessageDigest, md.Sum(nil))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ctAttr, err := NewAttribute(oid.AttributeContentType, sd.EncapContentInfo.EContentType)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
hvAttr, err := NewAttribute(oid.AttributeAppleHashAgilityV1, pldata)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
hv2Attr, err := NewAttribute(oid.AttributeAppleHashAgilityV2, AppleHashAgility{
|
|
Type: oid.DigestAlgorithmSHA256,
|
|
Content: asn1.RawValue{
|
|
Class: asn1.ClassUniversal,
|
|
Tag: asn1.TagOctetString,
|
|
Bytes: md.Sum(nil),
|
|
IsCompound: false,
|
|
},
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// sort attributes to match required order in marshaled form
|
|
si.SignedAttrs, err = sortAttributes(stAttr, mdAttr, ctAttr, hvAttr, hv2Attr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Signature is over the marshaled signed attributes
|
|
sm, err := si.SignedAttrs.MarshaledForSigning()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
smd := hash.New()
|
|
if _, err := smd.Write(sm); err != nil {
|
|
return err
|
|
}
|
|
if si.Signature, err = signer.Sign(rand.Reader, smd.Sum(nil), hash); err != nil {
|
|
return err
|
|
}
|
|
|
|
sd.addDigestAlgorithm(si.DigestAlgorithm)
|
|
|
|
sd.SignerInfos = append(sd.SignerInfos, si)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (sd *SignedData) addDigestAlgorithm(algo pkix.AlgorithmIdentifier) {
|
|
for _, existing := range sd.DigestAlgorithms {
|
|
if existing.Algorithm.Equal(algo.Algorithm) {
|
|
return
|
|
}
|
|
}
|
|
|
|
sd.DigestAlgorithms = append(sd.DigestAlgorithms, algo)
|
|
}
|
|
|
|
func (sd *SignedData) AddCertificate(cert *x509.Certificate) error {
|
|
for _, c := range sd.Certificates {
|
|
if bytes.Equal(c.Bytes, cert.Raw) {
|
|
return nil
|
|
}
|
|
}
|
|
var rv asn1.RawValue
|
|
if _, err := asn1.Unmarshal(cert.Raw, &rv); err != nil {
|
|
return err
|
|
}
|
|
sd.Certificates = append(sd.Certificates, rv)
|
|
return nil
|
|
}
|
|
|
|
func (sd *SignedData) AddTimestamps(url, proxy string, insecure bool) error {
|
|
var err error
|
|
var attrs = make([]Attribute, len(sd.SignerInfos))
|
|
|
|
for i := range attrs {
|
|
if attrs[i], err = GetTimestamp(url, proxy, insecure, sd.SignerInfos[i]); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
for i := range attrs {
|
|
sd.SignerInfos[i].UnsignedAttrs = append(sd.SignerInfos[i].UnsignedAttrs, attrs[i])
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (sd *SignedData) Detached() {
|
|
sd.EncapContentInfo.EContent = asn1.RawValue{}
|
|
}
|
|
|
|
func (sd *SignedData) ToDER() ([]byte, error) {
|
|
der, err := asn1.Marshal(*sd)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ci := ContentInfo{
|
|
ContentType: oid.ContentTypeSignedData,
|
|
Content: asn1.RawValue{
|
|
Class: asn1.ClassContextSpecific,
|
|
Tag: 0,
|
|
Bytes: der,
|
|
IsCompound: true,
|
|
},
|
|
}
|
|
return asn1.Marshal(ci)
|
|
}
|
|
|
|
func NewAttribute(typ asn1.ObjectIdentifier, val any) (attr Attribute, err error) {
|
|
var der []byte
|
|
if der, err = asn1.Marshal(val); err != nil {
|
|
return
|
|
}
|
|
|
|
var rv asn1.RawValue
|
|
if _, err = asn1.Unmarshal(der, &rv); err != nil {
|
|
return
|
|
}
|
|
|
|
if err = NewAnySet(rv).Encode(&attr.RawValue); err != nil {
|
|
return
|
|
}
|
|
|
|
attr.Type = typ
|
|
|
|
return
|
|
}
|
|
|
|
func newIssuerAndSerialNumber(cert *x509.Certificate) (rv asn1.RawValue, err error) {
|
|
sid := IssuerAndSerialNumber{
|
|
SerialNumber: new(big.Int).Set(cert.SerialNumber),
|
|
}
|
|
if _, err = asn1.Unmarshal(cert.RawIssuer, &sid.Issuer); err != nil {
|
|
return
|
|
}
|
|
var der []byte
|
|
if der, err = asn1.Marshal(sid); err != nil {
|
|
return
|
|
}
|
|
if _, err = asn1.Unmarshal(der, &rv); err != nil {
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
func digestAlgorithmForPublicKey(pub crypto.PublicKey) pkix.AlgorithmIdentifier {
|
|
if ecPub, ok := pub.(*ecdsa.PublicKey); ok {
|
|
switch ecPub.Curve {
|
|
case elliptic.P384():
|
|
return pkix.AlgorithmIdentifier{Algorithm: oid.DigestAlgorithmSHA384}
|
|
case elliptic.P521():
|
|
return pkix.AlgorithmIdentifier{Algorithm: oid.DigestAlgorithmSHA512}
|
|
}
|
|
}
|
|
return pkix.AlgorithmIdentifier{Algorithm: oid.DigestAlgorithmSHA256}
|
|
}
|
|
|
|
func sortAttributes(attrs ...Attribute) ([]Attribute, error) {
|
|
// Sort attrs by their encoded values (including tag and
|
|
// lengths) as specified in X690 Section 11.6 and implemented
|
|
// in go >= 1.15's asn1.Marshal().
|
|
sort.Slice(attrs, func(i, j int) bool {
|
|
return bytes.Compare(
|
|
attrs[i].RawValue.FullBytes,
|
|
attrs[j].RawValue.FullBytes) < 0
|
|
})
|
|
|
|
return attrs, nil
|
|
}
|