Documentation
¶
Overview ¶
Package codesign provides iOS code signing functionality.
This package implements Apple's code signing format natively in Go, allowing you to resign IPA files and .app bundles on any platform without requiring macOS or Apple's codesign tool.
Basic Usage ¶
To resign an app:
signer, err := codesign.NewSigner(p12Data, password, profileData)
if err != nil {
log.Fatal(err)
}
err = signer.SignApp(appPath)
Features ¶
- Cross-platform: Works on Linux, macOS, and Windows
- IPA and .app support: Resign both IPA files and extracted .app bundles
- Nested bundle signing: Automatically signs frameworks, plugins, and extensions
- Bundle ID modification: Change the app's bundle identifier during resign
Index ¶
- Constants
- func Base64Hash(hash []byte) string
- func ComputeCDHash(sigData []byte, blobOffset, blobSize uint32, hashType uint8) []byte
- func CopyAppBundle(src, dst string) error
- func EntitlementsToDER(entitlements map[string]interface{}) ([]byte, error)
- func EntitlementsToXML(entitlements map[string]interface{}) ([]byte, error)
- func ExtractCDHashes(binaryPath string) (sha1Hash, sha256Hash []byte, err error)
- func ExtractEntitlements(profile *ProvisioningProfile) ([]byte, error)
- func ExtractIPA(ipaPath string) (string, error)
- func FindAppBundle(extractedDir string) (string, error)
- func GenerateCodeResources(appPath string) ([]byte, error)
- func GetAppBundleID(appPath string) (string, error)
- func GetAppExecutableName(appPath string) (string, error)
- func GetBundleIDFromPlist(plistPath string) (string, error)
- func HashData(data []byte) []byte
- func MergeEntitlements(base, override map[string]interface{}) map[string]interface{}
- func NativeSignAppBundle(appPath string, identity *SigningIdentity, entitlements []byte, ...) error
- func NativeSignMachO(path string, identity *SigningIdentity, entitlements []byte, bundleID string) error
- func NativeSignMachOWithContext(path string, identity *SigningIdentity, entitlements []byte, bundleID string, ...) error
- func ParseEntitlementsXML(data []byte) (map[string]interface{}, error)
- func PrintSignatureDiff(diff *SignatureDiff, w io.Writer)
- func PrintSignatureInfo(info *SignatureInfo, w io.Writer, bundlePath string)
- func RepackageIPA(extractedDir, outputPath string) error
- func Resign(opts ResignOptions) error
- func ResignApp(appPath string, identity *SigningIdentity, profile *ProvisioningProfile, ...) error
- func UpdateEntitlementsForBundleID(entitlements map[string]interface{}, teamID, newBundleID string) map[string]interface{}
- func VerifySignatureFromCerts(info *SignatureInfo, certs []*x509.Certificate) bool
- func WriteCodeResources(appPath string) error
- type BlobIndexEntry
- type BundleDiff
- type BundleSigningContext
- type CMSInfo
- type CodeDirDiff
- type CodeDirectoryInfo
- type CodeResourcesRules
- type EntitlementsDiff
- type EntitlementsInfo
- type FieldDiff
- type ProvisioningProfile
- func (p *ProvisioningProfile) GetApplicationIdentifier() string
- func (p *ProvisioningProfile) GetCertificates() ([]*x509.Certificate, error)
- func (p *ProvisioningProfile) GetTeamID() string
- func (p *ProvisioningProfile) IsDeviceAllowed(udid string) bool
- func (p *ProvisioningProfile) IsExpired() bool
- func (p *ProvisioningProfile) MatchesCertificate(cert *x509.Certificate) bool
- type RequirementsInfo
- type ResignOptions
- type SignatureDiff
- type SignatureInfo
- type SigningIdentity
- type SuperBlobInfo
Constants ¶
const ( MachOHeader32Size = 28 MachOHeader64Size = 32 )
Mach-O header sizes
const ( MH_MAGIC = 0xfeedface // 32-bit little-endian MH_MAGIC_64 = 0xfeedfacf // 64-bit little-endian FAT_MAGIC = 0xcafebabe // Fat binary (big-endian) FAT_MAGIC_64 = 0xcafebabf // Fat binary 64 (big-endian) )
Mach-O magic numbers
const ( PageSizeBits = 12 PageSize = 1 << PageSizeBits // 4096 bytes )
Page size for code signing (4KB)
const ( CSMAGIC_REQUIREMENT = 0xfade0c00 CSMAGIC_REQUIREMENTS = 0xfade0c01 CSMAGIC_CODEDIRECTORY = 0xfade0c02 CSMAGIC_EMBEDDED_SIGNATURE = 0xfade0cc0 CSMAGIC_EMBEDDED_ENTITLEMENTS = 0xfade7171 CSMAGIC_EMBEDDED_DER_ENTITLEMENTS = 0xfade7172 CSMAGIC_BLOBWRAPPER = 0xfade0b01 )
Code signature magic numbers (from Apple's cs_blobs.h)
const ( CSSLOT_CODEDIRECTORY = 0 CSSLOT_INFOSLOT = 1 CSSLOT_REQUIREMENTS = 2 CSSLOT_RESOURCEDIR = 3 CSSLOT_APPLICATION = 4 CSSLOT_ENTITLEMENTS = 5 CSSLOT_DER_ENTITLEMENTS = 7 CSSLOT_ALTERNATE_CODEDIRECTORIES = 0x1000 CSSLOT_SIGNATURESLOT = 0x10000 )
Code signature slot indices
const ( CS_HASHTYPE_SHA1 = 1 CS_HASHTYPE_SHA256 = 2 )
Code directory hash types
const ( CS_SHA1_LEN = 20 CS_SHA256_LEN = 32 CS_CDHASH_LEN = 20 // Truncated hash length for CDHashes )
Hash sizes
const ( CS_EXECSEG_MAIN_BINARY = 0x1 CS_EXECSEG_ALLOW_UNSIGNED = 0x10 )
Executable segment flags
const ( LC_CODE_SIGNATURE = 0x1d LC_CODE_SIGNATURE_SIZE = 16 )
Load commands
const ( AppleRootCAURL = "https://www.apple.com/appleca/AppleIncRootCertificate.cer" AppleWWDRG3URL = "https://www.apple.com/certificateauthority/AppleWWDRCAG3.cer" )
Apple certificate URLs
const FatArchAlignment = 0x4000 // 16KB
Fat binary alignment
Variables ¶
This section is empty.
Functions ¶
func Base64Hash ¶
Base64Hash returns base64-encoded hash for plist
func ComputeCDHash ¶
ComputeCDHash computes the CDHash for a CodeDirectory
func CopyAppBundle ¶
CopyAppBundle copies a .app bundle directory from src to dst
func EntitlementsToDER ¶
EntitlementsToDER converts entitlements map to DER-encoded ASN.1 format This is required for iOS code signing alongside the XML plist format The format follows Apple's specific plist-to-DER encoding: - Top-level: APPLICATION 16 { INTEGER 1, WrappedValue } - Dictionary: [16] SEQUENCE { SEQUENCE { UTF8String key, WrappedValue }... } - Array: SEQUENCE { WrappedValue... } - Boolean: BOOLEAN - Integer: INTEGER - String: UTF8String
func EntitlementsToXML ¶
EntitlementsToXML converts entitlements map to XML plist bytes
func ExtractCDHashes ¶
ExtractCDHashes extracts CDHashes from signature data
func ExtractEntitlements ¶
func ExtractEntitlements(profile *ProvisioningProfile) ([]byte, error)
ExtractEntitlements extracts entitlements from a provisioning profile as XML plist bytes
func ExtractIPA ¶
ExtractIPA extracts an IPA file to a temporary directory Returns the path to the temp directory
func FindAppBundle ¶
FindAppBundle finds the .app bundle inside an extracted IPA Returns the full path to the .app directory
func GenerateCodeResources ¶
GenerateCodeResources generates the _CodeSignature/CodeResources plist This hashes ALL files recursively, including those inside nested bundles (.framework, .xctest, etc.)
func GetAppBundleID ¶
GetAppBundleID reads the bundle ID from an app's Info.plist
func GetAppExecutableName ¶
GetAppExecutableName reads the executable name from an app's Info.plist
func GetBundleIDFromPlist ¶
GetBundleIDFromPlist reads CFBundleIdentifier from an Info.plist
func MergeEntitlements ¶
MergeEntitlements merges override entitlements into base entitlements Override values take precedence
func NativeSignAppBundle ¶
func NativeSignAppBundle(appPath string, identity *SigningIdentity, entitlements []byte, bundleID string) error
NativeSignAppBundle signs all Mach-O binaries in an app bundle
func NativeSignMachO ¶
func NativeSignMachO(path string, identity *SigningIdentity, entitlements []byte, bundleID string) error
NativeSignMachO signs a Mach-O binary using our native implementation
func NativeSignMachOWithContext ¶
func NativeSignMachOWithContext(path string, identity *SigningIdentity, entitlements []byte, bundleID string, bundleCtx *BundleSigningContext) error
NativeSignMachOWithContext signs a Mach-O binary with optional bundle context
func ParseEntitlementsXML ¶
ParseEntitlementsXML parses XML plist entitlements into a map
func PrintSignatureDiff ¶
func PrintSignatureDiff(diff *SignatureDiff, w io.Writer)
PrintSignatureDiff prints a signature diff to a writer
func PrintSignatureInfo ¶
func PrintSignatureInfo(info *SignatureInfo, w io.Writer, bundlePath string)
PrintSignatureInfo prints signature information to a writer
func RepackageIPA ¶
RepackageIPA creates an IPA file from an extracted directory
func Resign ¶
func Resign(opts ResignOptions) error
Resign resigns a .app bundle with a new signing identity (in-place)
func ResignApp ¶
func ResignApp(appPath string, identity *SigningIdentity, profile *ProvisioningProfile, newBundleID string) error
ResignApp resigns an already extracted .app bundle in place
func UpdateEntitlementsForBundleID ¶
func UpdateEntitlementsForBundleID(entitlements map[string]interface{}, teamID, newBundleID string) map[string]interface{}
UpdateEntitlementsForBundleID updates the entitlements with a new bundle ID It updates application-identifier and keychain-access-groups
func VerifySignatureFromCerts ¶
func VerifySignatureFromCerts(info *SignatureInfo, certs []*x509.Certificate) bool
VerifySignatureFromCerts verifies that the CMS signature was made by one of the provided certificates
func WriteCodeResources ¶
WriteCodeResources generates and writes the CodeResources file
Types ¶
type BlobIndexEntry ¶
BlobIndexEntry represents a single blob in the SuperBlob index
type BundleDiff ¶
type BundleDiff struct {
RelativePath string
SuperBlobDiff FieldDiff
CodeDirDiffs []CodeDirDiff
RequirementsDiff FieldDiff
EntitlementsDiff EntitlementsDiff
CMSDiff FieldDiff
// Only in one app
OnlyIn1 bool
OnlyIn2 bool
}
BundleDiff represents differences for a single bundle
func CompareSignatures ¶
func CompareSignatures(info1, info2 *SignatureInfo) *BundleDiff
CompareSignatures compares two SignatureInfo structures
type BundleSigningContext ¶
type BundleSigningContext struct {
InfoPlistPath string // Path to Info.plist (for special slot 1)
CodeResourcesPath string // Path to CodeResources (for special slot 3)
TeamID string // Team ID to embed in CodeDirectory
}
BundleSigningContext contains context needed for signing a bundle's main executable
type CMSInfo ¶
type CMSInfo struct {
Size uint32
CDHashSHA1 []byte
CDHashSHA256 []byte
SignerCN string
SignerTeamID string
RawData []byte
}
CMSInfo contains CMS signature details
type CodeDirDiff ¶
type CodeDirDiff struct {
Slot uint32
HashType string
VersionDiff FieldDiff
FlagsDiff FieldDiff
IdentifierDiff FieldDiff
TeamIDDiff FieldDiff
PageSizeDiff FieldDiff
CodeLimitDiff FieldDiff
SpecialSlotDiffs []FieldDiff
CodeHashesSame bool
CodeHashesCount1 int
CodeHashesCount2 int
}
CodeDirDiff represents CodeDirectory differences
type CodeDirectoryInfo ¶
type CodeDirectoryInfo struct {
Slot uint32
Version uint32
Flags uint32
HashType uint8
HashSize uint8
Identifier string
TeamID string
PageSize uint32
CodeLimit uint32
ExecSegBase uint64
ExecSegLimit uint64
ExecSegFlags uint64
NSpecialSlots uint32
NCodeSlots uint32
SpecialHashes map[int][]byte // slot number (negative) -> hash
CodeHashes [][]byte
}
CodeDirectoryInfo contains CodeDirectory details
type CodeResourcesRules ¶
type CodeResourcesRules struct {
// Files that should be omitted (not hashed)
Omit []string
// Files that are optional (hash if present)
Optional []string
// Files that must be nested (e.g., frameworks)
Nested bool
// Weight for rule priority
Weight int
}
CodeResourcesRules defines the rules for hashing resources
type EntitlementsDiff ¶
type EntitlementsDiff struct {
Same bool
Added map[string]interface{} // In 2 but not in 1
Removed map[string]interface{} // In 1 but not in 2
Changed map[string][2]interface{} // Different values
}
EntitlementsDiff represents entitlements differences
type EntitlementsInfo ¶
EntitlementsInfo contains entitlements details
type ProvisioningProfile ¶
type ProvisioningProfile struct {
Name string `plist:"Name"`
TeamName string `plist:"TeamName"`
TeamIdentifier []string `plist:"TeamIdentifier"`
AppIDName string `plist:"AppIDName"`
ApplicationIdentifierPrefix []string `plist:"ApplicationIdentifierPrefix"`
Entitlements map[string]interface{} `plist:"Entitlements"`
DeveloperCertificates [][]byte `plist:"DeveloperCertificates"`
ProvisionedDevices []string `plist:"ProvisionedDevices"`
ProvisionsAllDevices bool `plist:"ProvisionsAllDevices"`
CreationDate time.Time `plist:"CreationDate"`
ExpirationDate time.Time `plist:"ExpirationDate"`
UUID string `plist:"UUID"`
Platform []string `plist:"Platform"`
}
ProvisioningProfile represents a parsed .mobileprovision file
func ParseProvisioningProfile ¶
func ParseProvisioningProfile(data []byte) (*ProvisioningProfile, error)
ParseProvisioningProfile parses a .mobileprovision file The file is a CMS (PKCS#7) signed container with a plist payload
func (*ProvisioningProfile) GetApplicationIdentifier ¶
func (p *ProvisioningProfile) GetApplicationIdentifier() string
GetApplicationIdentifier returns the application identifier from entitlements
func (*ProvisioningProfile) GetCertificates ¶
func (p *ProvisioningProfile) GetCertificates() ([]*x509.Certificate, error)
GetCertificates parses and returns the developer certificates from the profile
func (*ProvisioningProfile) GetTeamID ¶
func (p *ProvisioningProfile) GetTeamID() string
GetTeamID returns the team identifier from the profile
func (*ProvisioningProfile) IsDeviceAllowed ¶
func (p *ProvisioningProfile) IsDeviceAllowed(udid string) bool
IsDeviceAllowed checks if a specific device UDID is allowed by this profile
func (*ProvisioningProfile) IsExpired ¶
func (p *ProvisioningProfile) IsExpired() bool
IsExpired checks if the provisioning profile has expired
func (*ProvisioningProfile) MatchesCertificate ¶
func (p *ProvisioningProfile) MatchesCertificate(cert *x509.Certificate) bool
MatchesCertificate checks if the given certificate matches any certificate in the profile
type RequirementsInfo ¶
type RequirementsInfo struct {
Size uint32
Expression string // Human-readable requirements expression (best effort)
RawData []byte
}
RequirementsInfo contains requirements blob details
type ResignOptions ¶
type ResignOptions struct {
AppPath string // Path to the .app bundle
P12Data []byte
P12Password string
ProvisioningProfile []byte
NewBundleID string // Optional: if set, changes the bundle ID
}
ResignOptions contains all options for resigning a .app bundle
type SignatureDiff ¶
type SignatureDiff struct {
Path1 string
Path2 string
BundleDiffs []BundleDiff
}
SignatureDiff represents the differences between two signatures
func CompareBundles ¶
func CompareBundles(path1, path2 string, recursive bool) (*SignatureDiff, error)
CompareBundles compares signatures of two bundles (and optionally nested bundles)
type SignatureInfo ¶
type SignatureInfo struct {
BinaryPath string
BundlePath string
RelativePath string // Relative path from the root bundle (e.g., "PlugIns/Foo.xctest/Frameworks/Bar.framework")
SuperBlob SuperBlobInfo
CodeDirs []CodeDirectoryInfo
Requirements RequirementsInfo
Entitlements EntitlementsInfo
EntitlementsDER []byte
CMSSignature CMSInfo
}
SignatureInfo holds parsed code signature details
func GetBundleSignatureInfo ¶
func GetBundleSignatureInfo(bundlePath string, recursive bool) ([]*SignatureInfo, error)
GetBundleSignatureInfo gets signature info for a bundle and optionally its nested bundles
func ParseSignature ¶
func ParseSignature(binaryPath string) (*SignatureInfo, error)
ParseSignature parses the code signature from a Mach-O binary
func ParseSignatureFromData ¶
func ParseSignatureFromData(data []byte, binaryPath, bundlePath string) (*SignatureInfo, error)
ParseSignatureFromData parses code signature from binary data
type SigningIdentity ¶
type SigningIdentity struct {
Certificate *x509.Certificate
PrivateKey crypto.PrivateKey
CertChain []*x509.Certificate
TeamID string
}
SigningIdentity represents a code signing identity (certificate + private key)
func LoadSigningIdentity ¶
func LoadSigningIdentity(p12Data []byte, password string) (*SigningIdentity, error)
LoadSigningIdentity loads a signing identity from a PKCS#12 file or PEM key
func LoadSigningIdentityWithProfile ¶
func LoadSigningIdentityWithProfile(keyData []byte, password string, profile *ProvisioningProfile) (*SigningIdentity, error)
LoadSigningIdentityWithProfile loads a signing identity using a PEM key and extracts the matching certificate from the provisioning profile
type SuperBlobInfo ¶
type SuperBlobInfo struct {
Magic uint32
Length uint32
BlobCount uint32
Blobs []BlobIndexEntry
}
SuperBlobInfo contains SuperBlob header information