//go:build linux package provisioner import ( "bytes" "encoding/base64" "io" family "amadan.net/rastrillo/rastrillo/crypto" "filippo.io/age" ) type credential struct { Version int `json:"version"` Identity floorIdentity `json:"identity"` Keypair string `json:"keypair"` } func wrapCredential(k *family.Keypair, id floorIdentity, password []byte) ([]byte, error) { if len(password) == 0 || len(password) > 1024 || k == nil || k.SignPriv == nil || k.BoxPriv == nil || base64.RawURLEncoding.EncodeToString(k.SignPub()) != id.Public { return nil, ErrRefused } raw, e := family.MarshalKeypair(k) if e != nil { return nil, ErrRefused } body, e := canonicalJSON(credential{1, id, base64.RawURLEncoding.EncodeToString(raw)}) if e != nil { return nil, e } return encryptPrivate(body, password) } func unwrapCredential(ciphertext, password []byte) (floorIdentity, *family.Keypair, error) { body, e := decryptPrivate(ciphertext, password, 16384) if e != nil { return floorIdentity{}, nil, e } var c credential if closedJSON(body, &c) != nil || c.Version != 1 { return floorIdentity{}, nil, ErrRefused } raw, e := base64.RawURLEncoding.DecodeString(c.Keypair) if e != nil || base64.RawURLEncoding.EncodeToString(raw) != c.Keypair { return floorIdentity{}, nil, ErrRefused } k, e := family.UnmarshalKeypair(raw) if e != nil || base64.RawURLEncoding.EncodeToString(k.SignPub()) != c.Identity.Public { return floorIdentity{}, nil, ErrRefused } if _, ok := tokenBytes(c.Identity.Registry, 32); !ok { return floorIdentity{}, nil, ErrRefused } if _, ok := tokenBytes(c.Identity.Device, 32); !ok { return floorIdentity{}, nil, ErrRefused } return c.Identity, k, nil } func encryptPrivate(body, password []byte) ([]byte, error) { if len(body) > 64<<10 || len(password) == 0 || len(password) > 1024 { return nil, ErrRefused } recipient, e := age.NewScryptRecipient(string(password)) if e != nil { return nil, ErrRefused } var out bytes.Buffer w, e := age.Encrypt(&out, recipient) if e != nil { return nil, ErrUnavailable } if _, e = w.Write(body); e != nil { return nil, ErrUnavailable } if e = w.Close(); e != nil { return nil, ErrUnavailable } return out.Bytes(), nil } func decryptPrivate(ciphertext, password []byte, limit int) ([]byte, error) { if len(ciphertext) == 0 || len(ciphertext) > 128<<10 || len(password) == 0 || len(password) > 1024 || limit <= 0 || limit > 64<<10 { return nil, ErrRefused } identity, e := age.NewScryptIdentity(string(password)) if e != nil { return nil, ErrRefused } identity.SetMaxWorkFactor(18) r, e := age.Decrypt(bytes.NewReader(ciphertext), identity) if e != nil { return nil, ErrRefused } body, e := io.ReadAll(io.LimitReader(r, int64(limit)+1)) if e != nil || len(body) > limit { return nil, ErrRefused } return body, nil }